diff --git a/configuration/configuration.go b/configuration/configuration.go index bb79d94e5..4d1e326ec 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -10,6 +10,14 @@ import ( "time" ) +const ( + // defaultMaxEntries is the default max number of entries returned by the catalog endpoint + defaultMaxEntries = 1000 + + // defaultMaxTags is the default max number of tags returned by the tags endpoint + defaultMaxTags = 1000 +) + // Configuration is a versioned registry configuration, intended to be provided by a yaml file, and // optionally modified by environment variables. // @@ -58,6 +66,10 @@ type Configuration struct { // options to control the maximum number of entries returned by the catalog endpoint. Catalog Catalog `yaml:"catalog,omitempty"` + // Tags provides configuration for the tags list (/v2//tags/list) endpoint. + // It allows specifying the maximum number of tags returned by the endpoint. + Tags Tags `yaml:"tags,omitempty"` + // Proxy defines the configuration options for using the registry as a pull-through cache. Proxy Proxy `yaml:"proxy,omitempty"` @@ -263,6 +275,14 @@ type LetsEncrypt struct { DirectoryURL string `yaml:"directoryurl,omitempty"` } +// Tags provides configuration options for the "/v2//tags/list" endpoint. +type Tags struct { + // MaxTags limits the maximum number of tags returned by the tags endpoint. + // Requesting n tags to the tags endpoint will return at most MaxTags tags. + // Default to 1000 tags if not set. + MaxTags int `yaml:"maxtags,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. @@ -815,7 +835,14 @@ func Parse(rd io.Reader) (*Configuration, error) { } if v0_1.Catalog.MaxEntries <= 0 { - v0_1.Catalog.MaxEntries = 1000 + v0_1.Catalog.MaxEntries = defaultMaxEntries + } + + if v0_1.Tags.MaxTags <= 0 { + if v0_1.Tags.MaxTags < 0 { + return nil, errors.New("maxtags limit must be a non-negative integer value") + } + v0_1.Tags.MaxTags = defaultMaxTags } if v0_1.Storage.Type() == "" { diff --git a/configuration/configuration_test.go b/configuration/configuration_test.go index 167dbe4d7..af4828963 100644 --- a/configuration/configuration_test.go +++ b/configuration/configuration_test.go @@ -75,6 +75,9 @@ var configStruct = Configuration{ Enabled: true, }, }, + Tags: Tags{ + MaxTags: 1000, + }, Redis: Redis{ Options: RedisOptions{ Addrs: []string{"localhost:6379"}, @@ -139,6 +142,8 @@ notifications: - application/octet-stream actions: - pull +tags: + maxtags: 1000 http: tls: clientcas: @@ -192,6 +197,8 @@ notifications: - application/octet-stream actions: - pull +tags: + maxtags: 1000 http: headers: X-Content-Type-Options: [nosniff] @@ -559,5 +566,7 @@ func copyConfig(config Configuration) *Configuration { Manifests: config.Validation.Manifests, } + configCopy.Tags = config.Tags + return configCopy } diff --git a/docs/content/about/configuration.md b/docs/content/about/configuration.md index d7727ad0a..42e919c9e 100644 --- a/docs/content/about/configuration.md +++ b/docs/content/about/configuration.md @@ -223,6 +223,8 @@ middleware: - name: redirect options: baseurl: https://example.com/ +tags: + maxtags: 1000 http: addr: localhost:5000 prefix: /my/nested/registry/ @@ -800,6 +802,21 @@ location of a proxy for the layer stored by the S3 storage driver. |-----------|----------|-------------------------------------------------------------------------------------------------------------| | `baseurl` | yes | `SCHEME://HOST` at which layers are served. Can also contain port. For example, `https://example.com:5443`. | +## `tags` + +The `tags` subsection provides configuration to limit the maximum number of tags +returned by the tags API endpoint. When a client requests the list of tags for +a repository, the registry will return at most `maxtags` tags. + +```yaml +tags: + maxtags: 100 +``` + +| Parameter | Required | Description | +|-----------|----------|-------------------------------------------------------------------------------------| +| `maxtags` | no | Overrides the maximum number of tags returned by the tags endpoint, default: `1000` | + ## `http` ```yaml diff --git a/registry/handlers/api_test.go b/registry/handlers/api_test.go index 529f66b25..76a5c3519 100644 --- a/registry/handlers/api_test.go +++ b/registry/handlers/api_test.go @@ -2256,6 +2256,9 @@ func newTestEnv(t *testing.T, deleteEnabled bool) *testEnv { Catalog: configuration.Catalog{ MaxEntries: 5, }, + Tags: configuration.Tags{ + MaxTags: 1000, + }, } config.HTTP.Headers = headerConfig diff --git a/registry/handlers/tags.go b/registry/handlers/tags.go index 3c9c9c916..4d88a74ee 100644 --- a/registry/handlers/tags.go +++ b/registry/handlers/tags.go @@ -43,11 +43,13 @@ func (th *tagsHandler) GetTags(w http.ResponseWriter, r *http.Request) { // parse n, if n unparseable, or negative assign it to defaultReturnedEntries if n := q.Get("n"); n != "" { + if th.App.Config.Tags.MaxTags > 0 { + limit = th.App.Config.Tags.MaxTags + } parsedMax, err := strconv.Atoi(n) - if err != nil || parsedMax < 0 { + if err != nil || (limit > 0 && parsedMax > limit) || parsedMax < 0 { th.Errors = append(th.Errors, errcode.ErrorCodePaginationNumberInvalid.WithDetail(map[string]int{"n": parsedMax})) return - } limit = parsedMax }