Skip to content

Commit 1bcd539

Browse files
committed
dedicated ecosystem controllers
1 parent 5054e5b commit 1bcd539

7 files changed

Lines changed: 114 additions & 105 deletions

File tree

controllers/dependencyfirewall/controller.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,23 @@ func (d *DependencyProxyController) fetchFromUpstream(ctx context.Context, eco e
154154
return data, resp.Header, resp.StatusCode, nil
155155
}
156156

157+
func ensureReadMethod(c shared.Context) error {
158+
if c.Request().Method != http.MethodGet && c.Request().Method != http.MethodHead {
159+
return echo.NewHTTPError(http.StatusMethodNotAllowed, "Method not allowed")
160+
}
161+
return nil
162+
}
163+
164+
func (d *DependencyProxyController) passthroughUpstreamResponse(c shared.Context, headers http.Header, statusCode int, data []byte) error {
165+
for key, values := range headers {
166+
for _, value := range values {
167+
c.Response().Header().Add(key, value)
168+
}
169+
}
170+
171+
return c.Blob(statusCode, headers.Get("Content-Type"), data)
172+
}
173+
157174
func (d *DependencyProxyController) cacheData(cachePath string, data []byte) error {
158175
dir := filepath.Dir(cachePath)
159176
if err := os.MkdirAll(dir, 0755); err != nil {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package dependencyfirewall
2+
3+
// NPMDependencyProxyController handles npm dependency proxy requests.
4+
// It embeds DependencyProxyController to reuse shared helpers and state.
5+
type NPMDependencyProxyController struct {
6+
*DependencyProxyController
7+
}
8+
9+
func NewNPMDependencyProxyController(controller *DependencyProxyController) *NPMDependencyProxyController {
10+
return &NPMDependencyProxyController{DependencyProxyController: controller}
11+
}
12+
13+
// GoDependencyProxyController handles Go dependency proxy requests.
14+
// It embeds DependencyProxyController to reuse shared helpers and state.
15+
type GoDependencyProxyController struct {
16+
*DependencyProxyController
17+
}
18+
19+
func NewGoDependencyProxyController(controller *DependencyProxyController) *GoDependencyProxyController {
20+
return &GoDependencyProxyController{DependencyProxyController: controller}
21+
}
22+
23+
// PythonDependencyProxyController handles PyPI dependency proxy requests.
24+
// It embeds DependencyProxyController to reuse shared helpers and state.
25+
type PythonDependencyProxyController struct {
26+
*DependencyProxyController
27+
}
28+
29+
func NewPythonDependencyProxyController(controller *DependencyProxyController) *PythonDependencyProxyController {
30+
return &PythonDependencyProxyController{DependencyProxyController: controller}
31+
}

controllers/dependencyfirewall/golang.go

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ func (goEcosystem) writeResponse(c shared.Context, data []byte, path string, cac
102102
return c.Blob(http.StatusOK, c.Response().Header().Get("Content-Type"), data)
103103
}
104104

105-
func (d *DependencyProxyController) ProxyGo(c shared.Context) error {
105+
func (d *GoDependencyProxyController) ProxyGo(c shared.Context) error {
106106
requestPath := golang.trimPrefix(c.Request().URL.Path)
107107

108108
ctx, span := depProxyTracer.Start(c.Request().Context(), "dependency-proxy.go",
@@ -115,8 +115,8 @@ func (d *DependencyProxyController) ProxyGo(c shared.Context) error {
115115
defer span.End()
116116
c.SetRequest(c.Request().WithContext(ctx))
117117

118-
if c.Request().Method != http.MethodGet && c.Request().Method != http.MethodHead {
119-
return echo.NewHTTPError(http.StatusMethodNotAllowed, "Method not allowed")
118+
if err := ensureReadMethod(c); err != nil {
119+
return err
120120
}
121121

122122
configs, err := d.GetDependencyProxyConfigs(c)
@@ -137,7 +137,7 @@ func (d *DependencyProxyController) ProxyGo(c shared.Context) error {
137137
}
138138

139139
// proxyGoExplicitVersion handles Go proxy requests for a specific version (.info, .mod, .zip).
140-
func (d *DependencyProxyController) proxyGoExplicitVersion(c shared.Context, ctx context.Context, span trace.Span, eco ecosystem, configs DependencyProxyConfigs, requestPath string) error {
140+
func (d *GoDependencyProxyController) proxyGoExplicitVersion(c shared.Context, ctx context.Context, span trace.Span, eco ecosystem, configs DependencyProxyConfigs, requestPath string) error {
141141
cachePath := d.getCachePath(eco, requestPath)
142142

143143
notAllowed, notAllowedReason := d.CheckNotAllowedPackage(ctx, eco, requestPath, configs)
@@ -196,12 +196,7 @@ func (d *DependencyProxyController) proxyGoExplicitVersion(c shared.Context, ctx
196196

197197
if statusCode != http.StatusOK {
198198
slog.Debug("Upstream returned non-OK status", "proxy", "go", "status", statusCode)
199-
for key, values := range headers {
200-
for _, value := range values {
201-
c.Response().Header().Add(key, value)
202-
}
203-
}
204-
return c.Blob(statusCode, headers.Get("Content-Type"), data)
199+
return d.passthroughUpstreamResponse(c, headers, statusCode, data)
205200
}
206201

207202
_, releaseTime, hasReleaseTime := d.ExtractGoVersionAndReleaseTime(data)
@@ -235,7 +230,7 @@ func (d *DependencyProxyController) proxyGoExplicitVersion(c shared.Context, ctx
235230
}
236231

237232
// proxyGoLatest handles Go proxy requests for @latest and @v/list (version-resolution requests).
238-
func (d *DependencyProxyController) proxyGoLatest(c shared.Context, ctx context.Context, span trace.Span, eco ecosystem, configs DependencyProxyConfigs, requestPath, packageName string) error {
233+
func (d *GoDependencyProxyController) proxyGoLatest(c shared.Context, ctx context.Context, span trace.Span, eco ecosystem, configs DependencyProxyConfigs, requestPath, packageName string) error {
239234
cachePath := d.getCachePath(eco, requestPath)
240235

241236
span.SetAttributes(attribute.Bool("proxy.cache_hit", false))
@@ -251,12 +246,7 @@ func (d *DependencyProxyController) proxyGoLatest(c shared.Context, ctx context.
251246

252247
if statusCode != http.StatusOK {
253248
slog.Debug("Upstream returned non-OK status", "proxy", "go", "status", statusCode)
254-
for key, values := range headers {
255-
for _, value := range values {
256-
c.Response().Header().Add(key, value)
257-
}
258-
}
259-
return c.Blob(statusCode, headers.Get("Content-Type"), data)
249+
return d.passthroughUpstreamResponse(c, headers, statusCode, data)
260250
}
261251

262252
resolvedVersion, releaseTime, hasReleaseTime := d.ExtractGoVersionAndReleaseTime(data)
@@ -311,7 +301,7 @@ func (d *DependencyProxyController) proxyGoLatest(c shared.Context, ctx context.
311301
}
312302

313303
// ExtractGoVersionAndReleaseTime parses a Go proxy .info response and returns the resolved version and its release time.
314-
func (d *DependencyProxyController) ExtractGoVersionAndReleaseTime(data []byte) (string, time.Time, bool) {
304+
func (d *GoDependencyProxyController) ExtractGoVersionAndReleaseTime(data []byte) (string, time.Time, bool) {
315305
var info struct {
316306
Version string `json:"Version"`
317307
Time time.Time `json:"Time"`

controllers/dependencyfirewall/npm.go

Lines changed: 12 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func (npmEcosystem) writeResponse(c shared.Context, data []byte, path string, ca
9797

9898
// ProxyNPMTarball handles explicit-version npm requests (.tgz downloads).
9999
// Routes: GET /npm/:package/-/* and GET /npm/:scope/:name/-/*
100-
func (d *DependencyProxyController) ProxyNPMTarball(c shared.Context) error {
100+
func (d *NPMDependencyProxyController) ProxyNPMTarball(c shared.Context) error {
101101
configs, err := d.GetDependencyProxyConfigs(c)
102102
if err != nil {
103103
slog.Error("Error getting dependency proxy configs", "error", err)
@@ -116,8 +116,8 @@ func (d *DependencyProxyController) ProxyNPMTarball(c shared.Context) error {
116116
defer span.End()
117117
c.SetRequest(c.Request().WithContext(ctx))
118118

119-
if c.Request().Method != http.MethodGet && c.Request().Method != http.MethodHead {
120-
return echo.NewHTTPError(http.StatusMethodNotAllowed, "Method not allowed")
119+
if err := ensureReadMethod(c); err != nil {
120+
return err
121121
}
122122

123123
slog.Info("Proxy request", "proxy", "npm", "type", "tarball", "method", c.Request().Method, "path", requestPath)
@@ -180,12 +180,7 @@ func (d *DependencyProxyController) ProxyNPMTarball(c shared.Context) error {
180180

181181
if statusCode != http.StatusOK {
182182
slog.Debug("Upstream returned non-OK status", "proxy", "npm", "status", statusCode)
183-
for key, values := range headers {
184-
for _, value := range values {
185-
c.Response().Header().Add(key, value)
186-
}
187-
}
188-
return c.Blob(statusCode, headers.Get("Content-Type"), data)
183+
return d.passthroughUpstreamResponse(c, headers, statusCode, data)
189184
}
190185

191186
_, releaseTime := d.ExtractNPMVersionAndReleaseTimeFromMetadata(data)
@@ -212,7 +207,7 @@ func (d *DependencyProxyController) ProxyNPMTarball(c shared.Context) error {
212207

213208
// ProxyNPMMetadata handles metadata / version-resolution npm requests (no explicit version in path).
214209
// Routes: GET /npm/:package and GET /npm/:scope/:name
215-
func (d *DependencyProxyController) ProxyNPMMetadata(c shared.Context) error {
210+
func (d *NPMDependencyProxyController) ProxyNPMMetadata(c shared.Context) error {
216211
configs, err := d.GetDependencyProxyConfigs(c)
217212
if err != nil {
218213
slog.Error("Error getting dependency proxy configs", "error", err)
@@ -231,8 +226,8 @@ func (d *DependencyProxyController) ProxyNPMMetadata(c shared.Context) error {
231226
defer span.End()
232227
c.SetRequest(c.Request().WithContext(ctx))
233228

234-
if c.Request().Method != http.MethodGet && c.Request().Method != http.MethodHead {
235-
return echo.NewHTTPError(http.StatusMethodNotAllowed, "Method not allowed")
229+
if err := ensureReadMethod(c); err != nil {
230+
return err
236231
}
237232

238233
slog.Info("Proxy request", "proxy", "npm", "type", "metadata", "method", c.Request().Method, "path", requestPath)
@@ -253,12 +248,7 @@ func (d *DependencyProxyController) ProxyNPMMetadata(c shared.Context) error {
253248

254249
if statusCode != http.StatusOK {
255250
slog.Debug("Upstream returned non-OK status", "proxy", "npm", "status", statusCode)
256-
for key, values := range headers {
257-
for _, value := range values {
258-
c.Response().Header().Add(key, value)
259-
}
260-
}
261-
return c.Blob(statusCode, headers.Get("Content-Type"), data)
251+
return d.passthroughUpstreamResponse(c, headers, statusCode, data)
262252
}
263253

264254
resolvedVersion, releaseTime := d.ExtractNPMVersionAndReleaseTimeFromMetadata(data)
@@ -308,7 +298,7 @@ func (d *DependencyProxyController) ProxyNPMMetadata(c shared.Context) error {
308298
return npm.writeResponse(c, data, requestPath, false)
309299
}
310300

311-
func (d *DependencyProxyController) ProxyNPMAudit(c shared.Context) error {
301+
func (d *NPMDependencyProxyController) ProxyNPMAudit(c shared.Context) error {
312302
requestPath := npm.trimPrefix(c.Request().URL.Path)
313303

314304
ctx, span := depProxyTracer.Start(c.Request().Context(), "dependency-proxy.npm-audit",
@@ -338,18 +328,10 @@ func (d *DependencyProxyController) ProxyNPMAudit(c shared.Context) error {
338328
return echo.NewHTTPError(http.StatusBadGateway, "Failed to fetch from upstream")
339329
}
340330

341-
for key, values := range headers {
342-
for _, value := range values {
343-
c.Response().Header().Add(key, value)
344-
}
345-
}
346-
347-
fmt.Println(string(data))
348-
349-
return c.Blob(statusCode, headers.Get("Content-Type"), data)
331+
return d.passthroughUpstreamResponse(c, headers, statusCode, data)
350332
}
351333

352-
func (d *DependencyProxyController) fetchNPMAuditFromUpstream(ctx context.Context, requestPath string, headers http.Header, bodyBytes []byte) ([]byte, http.Header, int, error) {
334+
func (d *NPMDependencyProxyController) fetchNPMAuditFromUpstream(ctx context.Context, requestPath string, headers http.Header, bodyBytes []byte) ([]byte, http.Header, int, error) {
353335
requestPath = strings.TrimRight(requestPath, "/")
354336
url, err := url.JoinPath(npmRegistry, requestPath)
355337
if err != nil {
@@ -406,7 +388,7 @@ func (d *DependencyProxyController) fetchNPMAuditFromUpstream(ctx context.Contex
406388
}
407389

408390
// ExtractNPMVersionAndReleaseTimeFromMetadata parses NPM package metadata JSON and extracts the latest version and its release time.
409-
func (d *DependencyProxyController) ExtractNPMVersionAndReleaseTimeFromMetadata(data []byte) (string, time.Time) {
391+
func (d *NPMDependencyProxyController) ExtractNPMVersionAndReleaseTimeFromMetadata(data []byte) (string, time.Time) {
410392
var metadata struct {
411393
DistTags struct {
412394
Latest string `json:"latest"`

controllers/dependencyfirewall/python.go

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func (pypiEcosystem) parsePackage(path string) (string, string) {
5757
path = strings.TrimPrefix(path, "/")
5858
if after, ok := strings.CutPrefix(path, "simple/"); ok {
5959
return strings.TrimSuffix(after, "/"), ""
60-
} else if strings.HasPrefix(path, "/packages/") {
60+
} else if strings.HasPrefix(path, "packages/") {
6161
filename := filepath.Base(path)
6262
matches := pypiFilenameRe.FindStringSubmatch(filename)
6363
if len(matches) > 2 {
@@ -106,9 +106,7 @@ func (pypiEcosystem) writeResponse(c shared.Context, data []byte, path string, c
106106

107107
// ProxyPyPIPackage handles explicit-version PyPI package downloads (from /packages/).
108108
// Route: GET /pypi/packages/*
109-
func (d *DependencyProxyController) ProxyPyPIPackage(c shared.Context) error {
110-
pypi := pypiEcosystem{}
111-
109+
func (d *PythonDependencyProxyController) ProxyPyPIPackage(c shared.Context) error {
112110
configs, err := d.GetDependencyProxyConfigs(c)
113111
if err != nil {
114112
slog.Error("Error getting dependency proxy configs", "error", err)
@@ -127,8 +125,8 @@ func (d *DependencyProxyController) ProxyPyPIPackage(c shared.Context) error {
127125
defer span.End()
128126
c.SetRequest(c.Request().WithContext(ctx))
129127

130-
if c.Request().Method != http.MethodGet && c.Request().Method != http.MethodHead {
131-
return echo.NewHTTPError(http.StatusMethodNotAllowed, "Method not allowed")
128+
if err := ensureReadMethod(c); err != nil {
129+
return err
132130
}
133131

134132
slog.Info("Proxy request", "proxy", "pypi", "type", "package", "method", c.Request().Method, "path", requestPath)
@@ -191,12 +189,7 @@ func (d *DependencyProxyController) ProxyPyPIPackage(c shared.Context) error {
191189

192190
if statusCode != http.StatusOK {
193191
slog.Debug("Upstream returned non-OK status", "proxy", "pypi", "status", statusCode)
194-
for key, values := range headers {
195-
for _, value := range values {
196-
c.Response().Header().Add(key, value)
197-
}
198-
}
199-
return c.Blob(statusCode, headers.Get("Content-Type"), data)
192+
return d.passthroughUpstreamResponse(c, headers, statusCode, data)
200193
}
201194

202195
if err := d.CacheDataWithIntegrity(cachePath, data); err != nil {
@@ -212,9 +205,7 @@ func (d *DependencyProxyController) ProxyPyPIPackage(c shared.Context) error {
212205

213206
// ProxyPyPISimple handles PyPI /simple/ metadata requests, resolving the latest version before checking rules.
214207
// Route: GET /pypi/simple/:package
215-
func (d *DependencyProxyController) ProxyPyPISimple(c shared.Context) error {
216-
pypi := pypiEcosystem{}
217-
208+
func (d *PythonDependencyProxyController) ProxyPyPISimple(c shared.Context) error {
218209
configs, err := d.GetDependencyProxyConfigs(c)
219210
if err != nil {
220211
slog.Error("Error getting dependency proxy configs", "error", err)
@@ -234,8 +225,8 @@ func (d *DependencyProxyController) ProxyPyPISimple(c shared.Context) error {
234225
defer span.End()
235226
c.SetRequest(c.Request().WithContext(ctx))
236227

237-
if c.Request().Method != http.MethodGet && c.Request().Method != http.MethodHead {
238-
return echo.NewHTTPError(http.StatusMethodNotAllowed, "Method not allowed")
228+
if err := ensureReadMethod(c); err != nil {
229+
return err
239230
}
240231

241232
slog.Info("Proxy request", "proxy", "pypi", "type", "simple", "method", c.Request().Method, "path", requestPath)
@@ -254,12 +245,7 @@ func (d *DependencyProxyController) ProxyPyPISimple(c shared.Context) error {
254245

255246
if statusCode != http.StatusOK {
256247
slog.Debug("Upstream returned non-OK status", "proxy", "pypi", "status", statusCode)
257-
for key, values := range headers {
258-
for _, value := range values {
259-
c.Response().Header().Add(key, value)
260-
}
261-
}
262-
return c.Blob(statusCode, headers.Get("Content-Type"), data)
248+
return d.passthroughUpstreamResponse(c, headers, statusCode, data)
263249
}
264250

265251
// Fetch the PyPI JSON API to resolve version and release time before checking rules —
@@ -315,7 +301,7 @@ func (d *DependencyProxyController) ProxyPyPISimple(c shared.Context) error {
315301
return pypi.writeResponse(c, data, requestPath, false)
316302
}
317303

318-
func (d *DependencyProxyController) fetchPyPIFromUpstream(ctx context.Context, requestPath string, headers http.Header) ([]byte, http.Header, int, error) {
304+
func (d *PythonDependencyProxyController) fetchPyPIFromUpstream(ctx context.Context, requestPath string, headers http.Header) ([]byte, http.Header, int, error) {
319305
requestPath = strings.TrimRight(requestPath, "/")
320306
url, err := url.JoinPath(pypiRegistry, requestPath)
321307
if err != nil {
@@ -351,7 +337,7 @@ func (d *DependencyProxyController) fetchPyPIFromUpstream(ctx context.Context, r
351337

352338
// ExtractPyPIReleaseTime parses a PyPI JSON API response and returns the resolved version and its upload time.
353339
// If version is empty, it uses info.version (the current release).
354-
func (d *DependencyProxyController) ExtractPyPIReleaseTime(data []byte, version string) (string, time.Time, bool) {
340+
func (d *PythonDependencyProxyController) ExtractPyPIReleaseTime(data []byte, version string) (string, time.Time, bool) {
355341
var metadata struct {
356342
Info struct {
357343
Version string `json:"version"`
@@ -378,7 +364,7 @@ func (d *DependencyProxyController) ExtractPyPIReleaseTime(data []byte, version
378364
}
379365

380366
// fetchPyPILatestVersionAndReleaseTime fetches the PyPI JSON API and returns the resolved version and its release time.
381-
func (d *DependencyProxyController) fetchPyPILatestVersionAndReleaseTime(ctx context.Context, pkgName string) (string, time.Time, bool) {
367+
func (d *PythonDependencyProxyController) fetchPyPILatestVersionAndReleaseTime(ctx context.Context, pkgName string) (string, time.Time, bool) {
382368
data, _, statusCode, err := d.fetchPyPIFromUpstream(ctx, "/pypi/"+pkgName+"/json", http.Header{})
383369
if err != nil || statusCode != http.StatusOK {
384370
return "", time.Time{}, false

controllers/providers.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,7 @@ var ControllerModule = fx.Options(
112112
fx.Provide(ProvideDependencyProxyCache),
113113
fx.Provide(fx.Annotate(ProvideMaliciousPackageChecker, fx.As(new(shared.MaliciousPackageChecker)))),
114114
fx.Provide(dependencyfirewall.NewDependencyProxyController),
115+
fx.Provide(dependencyfirewall.NewNPMDependencyProxyController),
116+
fx.Provide(dependencyfirewall.NewGoDependencyProxyController),
117+
fx.Provide(dependencyfirewall.NewPythonDependencyProxyController),
115118
)

0 commit comments

Comments
 (0)