Skip to content

Commit f477e93

Browse files
fix path boundary issue and add unit tests
1 parent 7a18af1 commit f477e93

2 files changed

Lines changed: 81 additions & 1 deletion

File tree

internal/handlers/terraform_registry.go

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package handlers
22

33
import (
44
"net/http"
5+
"sort"
6+
"strings"
57
"sync"
68

79
"github.com/elazarl/goproxy"
@@ -65,6 +67,12 @@ func NewTerraformRegistryHandler(credentials config.Credentials) *TerraformRegis
6567
}
6668
handler.credentials = append(handler.credentials, terraformCred)
6769
}
70+
71+
// Sort credentials by URL path length descending (longest first)
72+
sort.Slice(handler.credentials, func(i, j int) bool {
73+
return len(handler.credentials[i].url) > len(handler.credentials[j].url)
74+
})
75+
6876
return &handler
6977
}
7078

@@ -80,7 +88,7 @@ func (h *TerraformRegistryHandler) HandleRequest(request *http.Request, context
8088

8189
// Fall back to static credentials
8290
for _, cred := range h.credentials {
83-
if !helpers.UrlMatchesRequest(request, cred.url, true) && !helpers.CheckHost(request, cred.host) {
91+
if !urlMatchesRequestWithBoundary(request, cred.url) && !helpers.CheckHost(request, cred.host) {
8492
continue
8593
}
8694

@@ -91,3 +99,53 @@ func (h *TerraformRegistryHandler) HandleRequest(request *http.Request, context
9199

92100
return request, nil
93101
}
102+
103+
// urlMatchesRequestWithBoundary checks if the request URL matches the credential URL
104+
// with proper path boundary checking.
105+
func urlMatchesRequestWithBoundary(request *http.Request, credURL string) bool {
106+
if credURL == "" {
107+
return false
108+
}
109+
110+
parsedURL, err := helpers.ParseURLLax(credURL)
111+
if err != nil {
112+
return false
113+
}
114+
115+
if !helpers.AreHostnamesEqual(parsedURL.Hostname(), request.URL.Hostname()) {
116+
return false
117+
}
118+
119+
urlPort := parsedURL.Port()
120+
if urlPort == "" {
121+
urlPort = "443"
122+
}
123+
124+
reqPort := request.URL.Port()
125+
if reqPort == "" {
126+
reqPort = "443"
127+
}
128+
129+
if urlPort != reqPort {
130+
return false
131+
}
132+
133+
credPath := strings.TrimRight(parsedURL.Path, "/")
134+
reqPath := request.URL.Path
135+
136+
if credPath == "" {
137+
// Empty path matches everything on the host
138+
return true
139+
}
140+
141+
if reqPath == credPath {
142+
return true
143+
}
144+
145+
// Check if request path starts with credPath followed by /
146+
if strings.HasPrefix(reqPath, credPath+"/") {
147+
return true
148+
}
149+
150+
return false
151+
}

internal/handlers/terraform_registry_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,26 @@ func TestTerraformRegistryHandler(t *testing.T) {
110110
handler := NewTerraformRegistryHandler(credentials)
111111
assert.Equal(t, 0, len(handler.credentials), "should skip credential with empty host and url")
112112
})
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+
})
113135
}

0 commit comments

Comments
 (0)