Skip to content

Commit

Permalink
Fix runaway allocation on /v2/_catalog
Browse files Browse the repository at this point in the history
Introduced a Catalog entry in the configuration struct. With it,
it's possible to control the maximum amount of entries returned
by /v2/catalog (`GetCatalog` in registry/handlers/catalog.go).

It's set to a default value of 1000.

`GetCatalog` returns 100 entries by default if no `n` is
provided. When provided it will be validated to be between `0`
and `MaxEntries` defined in Configuration. When `n` is outside
the aforementioned boundary, ErrorCodePaginationNumberInvalid is
returned.

`GetCatalog` now handles `n=0` gracefully with an empty response
as well.

Signed-off-by: José D. Gómez R. <1josegomezr@gmail.com>
Co-authored-by: Cory Snider <corhere@gmail.com>
  • Loading branch information
josegomezr and corhere committed Apr 24, 2023
1 parent dc5b207 commit 521ea3d
Show file tree
Hide file tree
Showing 6 changed files with 376 additions and 42 deletions.
18 changes: 17 additions & 1 deletion configuration/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,8 @@ type Configuration struct {
} `yaml:"pool,omitempty"`
} `yaml:"redis,omitempty"`

Health Health `yaml:"health,omitempty"`
Health Health `yaml:"health,omitempty"`
Catalog Catalog `yaml:"catalog,omitempty"`

Proxy Proxy `yaml:"proxy,omitempty"`

Expand Down Expand Up @@ -244,6 +245,16 @@ type Configuration struct {
} `yaml:"policy,omitempty"`
}

// Catalog is composed of MaxEntries.
// Catalog endpoint (/v2/_catalog) configuration, it provides the configuration
// options to control the maximum number of entries returned by the catalog endpoint.
type Catalog struct {
// Max number of entries returned by the catalog endpoint. Requesting n entries
// to the catalog endpoint will return at most MaxEntries entries.
// An empty or a negative value will set a default of 1000 maximum entries by default.
MaxEntries int `yaml:"maxentries,omitempty"`
}

// LogHook is composed of hook Level and Type.
// After hooks configuration, it can execute the next handling automatically,
// when defined levels of log message emitted.
Expand Down Expand Up @@ -670,6 +681,11 @@ func Parse(rd io.Reader) (*Configuration, error) {
if v0_1.Loglevel != Loglevel("") {
v0_1.Loglevel = Loglevel("")
}

if v0_1.Catalog.MaxEntries <= 0 {
v0_1.Catalog.MaxEntries = 1000
}

if v0_1.Storage.Type() == "" {
return nil, errors.New("no storage configuration provided")
}
Expand Down
4 changes: 4 additions & 0 deletions configuration/configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ var configStruct = Configuration{
},
},
},
Catalog: Catalog{
MaxEntries: 1000,
},
HTTP: struct {
Addr string `yaml:"addr,omitempty"`
Net string `yaml:"net,omitempty"`
Expand Down Expand Up @@ -524,6 +527,7 @@ func copyConfig(config Configuration) *Configuration {
configCopy.Version = MajorMinorVersion(config.Version.Major(), config.Version.Minor())
configCopy.Loglevel = config.Loglevel
configCopy.Log = config.Log
configCopy.Catalog = config.Catalog
configCopy.Log.Fields = make(map[string]interface{}, len(config.Log.Fields))
for k, v := range config.Log.Fields {
configCopy.Log.Fields[k] = v
Expand Down
17 changes: 17 additions & 0 deletions registry/api/v2/descriptors.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,19 @@ var (
},
}

invalidPaginationResponseDescriptor = ResponseDescriptor{
Name: "Invalid pagination number",
Description: "The received parameter n was invalid in some way, as described by the error code. The client should resolve the issue and retry the request.",
StatusCode: http.StatusBadRequest,
Body: BodyDescriptor{
ContentType: "application/json",
Format: errorsBody,
},
ErrorCodes: []errcode.ErrorCode{
ErrorCodePaginationNumberInvalid,
},
}

repositoryNotFoundResponseDescriptor = ResponseDescriptor{
Name: "No Such Repository Error",
StatusCode: http.StatusNotFound,
Expand Down Expand Up @@ -490,6 +503,7 @@ var routeDescriptors = []RouteDescriptor{
},
},
Failures: []ResponseDescriptor{
invalidPaginationResponseDescriptor,
unauthorizedResponseDescriptor,
repositoryNotFoundResponseDescriptor,
deniedResponseDescriptor,
Expand Down Expand Up @@ -1578,6 +1592,9 @@ var routeDescriptors = []RouteDescriptor{
},
},
},
Failures: []ResponseDescriptor{
invalidPaginationResponseDescriptor,
},
},
},
},
Expand Down
9 changes: 9 additions & 0 deletions registry/api/v2/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,13 @@ var (
longer proceed.`,
HTTPStatusCode: http.StatusNotFound,
})

ErrorCodePaginationNumberInvalid = errcode.Register(errGroup, errcode.ErrorDescriptor{
Value: "PAGINATION_NUMBER_INVALID",
Message: "invalid number of results requested",
Description: `Returned when the "n" parameter (number of results
to return) is not an integer, "n" is negative or "n" is bigger than
the maximum allowed.`,
HTTPStatusCode: http.StatusBadRequest,
})
)
Loading

0 comments on commit 521ea3d

Please sign in to comment.