* fix(authz): metrics: reject users not in list even with anonymous read
Even when anonymous reads are enabled for metrics, users not in the
allowed list should not be allowed.
This change also refactors the MetricsAuthzHandler to align better
with this logic.
Signed-off-by: Vishwas Rajashekar <dev@vrajashkr.com>
* fix(authz): address review comments
Address comments to pass username when present
to AuthzFail if user is not allowed for metrics.
This changes the response to Forbidden instead of
Unauthorized.
Use isAnonymous() check instead of only checking for
empty username.
Signed-off-by: Vishwas Rajashekar <dev@vrajashkr.com>
* fix(authz): fix additional review comments
Fix a few more review comments
Signed-off-by: Vishwas Rajashekar <dev@vrajashkr.com>
---------
Signed-off-by: Vishwas Rajashekar <dev@vrajashkr.com>
This change adds validation for metrics config.
In particular, the metrics path is checked to
ensure it starts with a / and is not one of the
disallowed paths.
Signed-off-by: Vishwas Rajashekar <dev@vrajashkr.com>
GetAllDedupeReposCandidates propagated zerr.ErrCacheMiss from the cache's
GetAllBlobs verbatim. But a cache miss is the normal case for a not-yet-cached
blob — the first push of a new blob, or a cross-repo mount check during a push
— and semantically means "no dedupe/mount candidates", not a failure.
Propagating it caused canMount (used by the CheckBlob and CreateBlobUpload
handlers) to surface the error, which the route handlers log as an
"unexpected error". With remote (e.g. S3) storage a cache is always present
(dedupe:false does not disable it), so this logs an error-level line for every
fresh blob digest on every push — significant log spam during bulk pushes and
cross-repo mounts, with no functional impact (the push still succeeds via a
normal upload).
Handle ErrCacheMiss the same way as the existing nil-cache branch above:
return no candidates and no error.
Signed-off-by: Janko Thyson <janko@kaosmaps.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(metadb): add writer version to interface
Signed-off-by: Jacob McSwain <jacob@mcswain.dev>
* chore(metadb): add writer version to db mock
Signed-off-by: Jacob McSwain <jacob@mcswain.dev>
* chore(metadb): implement writer version for bolt, redis, and dynamodb
Signed-off-by: Jacob McSwain <jacob@mcswain.dev>
* feat(metadb): add optional fast restart path that skips storage walk when binary identity matches metaDB stamp
binary identity is determined by the current release tag/commit and stored in metaDB after a successful storage parse. When fast restart is enabled, the next startup will skip the parse if the stored identity matches the current binary
Signed-off-by: Jacob McSwain <jacob@mcswain.dev>
* chore(cli): serve: add a way to force reparse storage
Signed-off-by: Jacob McSwain <jacob@mcswain.dev>
* refactor(meta): version: split to avoid global state mutation in tests
Signed-off-by: Jacob McSwain <jacob@mcswain.dev>
* fix(meta): version: include commit in writerVersion to distinguish retags
Signed-off-by: Jacob McSwain <jacob@mcswain.dev>
* chore(config): add IsFastRestartEnabled() test
Signed-off-by: Jacob McSwain <jacob@mcswain.dev>
* fix(meta): skip writer-version stamp when storage parse is incomplete
ParseStorage returns nil even when individual repos fail to parse or are only partially parsed (a missing manifest blob), so MaybeParseStorage would stamp a partially-populated metaDB as good. On the next restart fastRestart trusts the stamp, skips the storage walk, and never recovers.
Track per-repo outcomes via parseStats and stamp only when the walk fully populated the metaDB, otherwise log and continue so the next restart reparses
Signed-off-by: Jacob McSwain <jacob@mcswain.dev>
* fix(docs): readme: remove trailing comma from JSON config
Signed-off-by: Jacob McSwain <jacob@mcswain.dev>
* fix(meta): dynamodb: use context.Background instead of context.TODO
Signed-off-by: Jacob McSwain <jacob@mcswain.dev>
* fix(meta): invalidate fast restart on storage config changes
Signed-off-by: Jacob McSwain <jacob@mcswain.dev>
* chore(meta): dynamodb: use context.Background() instead of context.TODO()
Signed-off-by: Jacob McSwain <jacob@mcswain.dev>
* docs(meta): dynamodb: add comment about nil AttributeValue handling in GetWriterVersion
Signed-off-by: Jacob McSwain <jacob@mcswain.dev>
* chore: rename writer-version stamp to fast-restart stamp
also replaces the version/commit tracking to use BinaryVersion instead of WriterVersion
This should make things more clear
Signed-off-by: Jacob McSwain <jacob@mcswain.dev>
* fix(config): ensure FastRestart is on GlobalStorageConfig
This is not a per-subpath setting
Signed-off-by: Jacob McSwain <jacob@mcswain.dev>
* fix(metadb): redis: tests: ensure clients are closed
Signed-off-by: Jacob McSwain <jacob@mcswain.dev>
---------
Signed-off-by: Jacob McSwain <jacob@mcswain.dev>
* ci: migrate workflows off deprecated oracle-vm runners
Switch CI jobs from oracle-vm-* runner labels to cncf-ubuntu-* as the oracle-cm-* CNCF runner labels are deprecated.
Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
* ci: increase resources available for ecosystem tools tests
Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
---------
Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
* feat(metrics): add Prometheus GC metrics
Track garbage collection activity with three new metrics:
- zot_gc_runs_total (counter, label: error) — GC run count
- zot_gc_duration_seconds (summary) — GC run duration
- zot_gc_deleted_total (counter, label: type) — items deleted
by type: blob, manifest, upload
MetricServer is added to GarbageCollect and wired through
all callers (controller, verify-feature retention, tests).
Signed-off-by: Benoit Tigeot <benoit.tigeot@lifen.fr>
* fix(test): add missing metrics var in GCS GC tests
TestGCSGarbageCollectImageIndex and
TestGCSGarbageCollectChainedImageIndexes were missing the
metrics variable required by NewGarbageCollect after the
MetricServer parameter was added.
Signed-off-by: Benoit Tigeot <benoit.tigeot@lifen.fr>
* fix(test): add defer metrics.Stop() in GC tests
Prevent goroutine/port leaks by stopping MetricsServer in
storage_test.go (3 functions) and gcs_test.go (also add
missing metrics declaration in TestGCSGarbageCollectImageManifest).
Signed-off-by: Benoit Tigeot <benoit.tigeot@lifen.fr>
* fix(test): cover `CleanRepo` error path
Add test that exercises the error branch in
`CleanRepo` where `cleanRepo` fails, covering
the metrics calls and log lines flagged by Codecov.
Signed-off-by: Benoit Tigeot <benoit.tigeot@lifen.fr>
* test: Cover GC error paths for codecov
Add three tests in gc_internal_test.go to cover previously
untested error branches in `removeBlobUploads` and
`removeUnreferencedBlobs`: `ListBlobUploads` failure,
`addIndexBlobsToReferences` failure, and `PathNotFoundError`
from `GetAllBlobs`.
Signed-off-by: Benoit Tigeot <benoit.tigeot@lifen.fr>
* test(gc): cover remaining error paths
Cover `StatBlobUpload`, `digest.Validate()`,
`isBlobOlderThan`, and `CleanupRepo` error branches
in `removeBlobUploads` and `removeUnreferencedBlobs`.
`removeUnreferencedBlobs` now at 100% coverage,
`removeBlobUploads` from 78.3% to 91.3%.
Signed-off-by: Benoit Tigeot <benoit.tigeot@lifen.fr>
* test: cover `sanityChecks` label name mismatch
Try to avoid -0.09% coverage regression on `minimal.go`
by exercising the uncovered branch in `sanityChecks`
where label names have correct count but wrong values.
Signed-off-by: Benoit Tigeot <benoit.tigeot@lifen.fr>
* test(gc): exercise real GC path in metrics test
TestGCMetrics was calling metric helpers directly instead of
running actual garbage collection, so it couldn't catch wiring
regressions where `CleanRepo` stops recording metrics.
Now uploads an orphaned blob and runs `gc.CleanRepo` end-to-end,
verifying metrics appear on the Prometheus endpoint.
Suggestion from Copilot: https://github.com/project-zot/zot/pull/3863#discussion_r3129324719
Signed-off-by: Benoit Tigeot <benoit.tigeot@lifen.fr>
* fix(gc): skip deletion metrics when DryRun is enabled
https://github.com/project-zot/zot/pull/3863#discussion_r3129324684
Signed-off-by: Benoit Tigeot <benoit.tigeot@lifen.fr>
* fix(test): stop leaked MetricsServer goroutines in GCS tests
https://github.com/project-zot/zot/pull/3863#discussion_r3129324657
Signed-off-by: Benoit Tigeot <benoit.tigeot@lifen.fr>
* refactor(test): drop unnecessary zlog import alias
Signed-off-by: Benoit Tigeot <benoit.tigeot@lifen.fr>
* fix(monitoring): expose metric types outside build tag
`MetricsCopy` and related types were only visible under `\!metrics`,
causing a typecheck failure when golangci-lint runs with `-tags metrics`.
Moving the type definitions to `common.go` makes them unconditionally available.
Signed-off-by: Benoit Tigeot <benoit.tigeot@lifen.fr>
* fix(monitoring): remove extra blank line for gci
Signed-off-by: Benoit Tigeot <benoit.tigeot@lifen.fr>
* test(gc): cover both dry-run and real deletion metrics
And fix issue with build tag with metrics
Signed-off-by: Benoit Tigeot <benoit.tigeot@lifen.fr>
* Satisfy testpackage linter for gc metrics test
The `testpackage` linter allows `package gc` only in files named
`*_internal_test.go`; rename to follow that convention.
Signed-off-by: Benoit Tigeot <benoit.tigeot@lifen.fr>
---------
Signed-off-by: Benoit Tigeot <benoit.tigeot@lifen.fr>
Remove warning:
```
WARN The linter 'gomodguard' is deprecated (since v2.12.0) due to: new major version. Replaced by gomodguard_v2.
WARN Suggested new configuration:
linters:
enable:
- gomodguard_v2
```
Signed-off-by: Benoit Tigeot <benoit.tigeot@lifen.fr>
1. Cause
SBOM assets were added to the release checksum file, so you get two lines that contain the same substring, e.g. …linux-amd64.tar.gz and …linux-amd64.tar.gz.sbom.json.
The old install.sh logic used something like grep "${BASENAME}", so both lines matched. That breaks want (wrong or ambiguous hash) and shows up as checksum verification errors (including the 8df… vs fd3… style mismatch you debugged).
2. The upstream fix is in https://github.com/golangci/golangci-lint/pull/6539
The matcher was changed so the checksum line must end with the archive name — i.e. grep "${BASENAME}$" — so the .sbom.json line no longer matches.
3. In the same thread they say master on raw.githubusercontent.com is not the right branch anymore and recommend the canonical installer URL:
https://golangci-lint.run/install.sh
(see the PR description and local install / binaries.)
Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
Make `zli config` and config subcommands show usage/help when invoked with
missing required args, instead of Cobra’s generic “accepts N arg(s)” errors.
Example:
- before: `zli config add` -> `Error: accepts 2 arg(s), received 0`
- after: `zli config add` prints `zli config add <config-name> <url>` usage/help1~fix(zli config): print help for missing args
Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
* feat(zli): add config list/show/get/set/reset and isolate deprecated syntax
Introduce first-class subcommands for listing profiles, showing a profile,
getting and setting keys, and resetting optional keys (alongside existing add/remove).
The parent command now resolves ~/.zot via zliUserConfigPath(),
documents that profile names must not clash with subcommand names,
and states that positional/--list/--reset usage is deprecated and will be removed soon.
Legacy behavior is delegated to config_cmd_deprecated.go with stderr warnings for old flags and positional get/set.
Examples and inline help point users at the new commands.
FormatNames/FormatListedVars comments reference config list/show.
Tests are split so config_cmd_test.go exercises the supported subcommands
while config_cmd_deprecated_test.go retains coverage for the deprecated
paths under renamed TestConfigCmdDeprecated* entries.
Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
* test: stabilize retention check tests
See https://github.com/project-zot/zot/actions/runs/25361779632/job/74362802944?pr=4037
Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
---------
Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
Keep CLI binaries from importing pkg/api/config just for version strings by
centralizing Commit/ReleaseTag/BinaryType/GoVersion in a tiny buildinfo package.
Update ldflags targets and callers accordingly.
Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
feat(cli): add typed ~/.zot config layer and strict validation
Introduce pkg/cli/client/config.go with ZliConfigFile/ZliConfig and
ReadZliConfigFile, replacing the loose map[string]any load/save path in
config_cmd.go.
Parsing now rejects malformed JSON with ErrCliBadConfig and requires a
non-null configs array (ErrCliMissingConfigsField when wrapped). Each
profile must have non-empty _name and url.
Config commands delegate to typed helpers (Find, AddEntry, RemoveEntry,
GetVar/SetVar/ResetVar, FormatNames, WriteFile). Fresh or minimal files
still behave as empty via isFreshCliRead (ErrEmptyJSON or missing configs).
Tests: prefer t.Setenv("HOME", t.TempDir()) where CLI resolution uses --url
only; align CVE/client/search tests with mandatory profile URL and HOME
isolation.
Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
Read identity from request context after handlers run so Basic, Bearer,
OIDC, mTLS, etc. are covered; use subject "anonymous" when unset.
Redact Authorization in SessionLogger without decoding credentials.
Add session_test.go for SessionLogger and SessionAuditLogger.
Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
Rename getOpenIDUsername to getOpenIDIdentity and thread "identity"
through bearer OIDC, Basic-auth parsing, OAuth2Callback, and log fields.
Only fall back (and warn) to the default email claim when the configured
username claim is non-default but missing or empty.
Stop emitting Info logs when groups are absent on only UserInfo or only ID
token claims; log once at Debug when no groups remain after merging both.
Update ClaimMapping docs to mention username and groups claims; fix mTLS
extractIdentity comment typo; clarify GetAuthUserFromRequestSession doc.
Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
countingReader was not respecting the single responsibility principle
and the implementation was hard to understand
Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
* feat(auth): map OpenID groups claim
Signed-off-by: Akash Kumar <meakash7902@gmail.com>
* fix(auth): refine OIDC claim mapping logs
Signed-off-by: Akash Kumar <meakash7902@gmail.com>
* refactor(auth): collapse OIDC username fallback into nested if
Reuse the empty-username branch for the email fallback so the value is
checked once and the failure path lives next to the recovery attempt.
Signed-off-by: Akash Kumar <meakash7902@gmail.com>
* refactor(auth): consolidate OIDC claim extraction into authn.go
Move getOpenIDClaimMapping, getOpenIDUsername, and appendOpenIDGroups
out of routes.go into authn.go alongside a new extractOpenIDIdentity
helper that owns the username/groups extraction flow. This keeps the
HTTP callback in routes.go thin and groups OIDC plumbing with the rest
of the authentication code.
Also:
- Filter nil and empty entries consistently across the []any, []string,
and string branches of appendOpenIDGroups, with new test cases
covering []any{nil, ""} and []string{"admin","",...}.
- Surface a Warn log when an operator-configured username claim is
missing/empty so the fallback to email isn't silent.
- Rename openid_claim_mapping_internal_test.go to authn_internal_test.go
and drop the build tags that aren't needed for the internal tests.
Signed-off-by: Akash Kumar <meakash7902@gmail.com>
---------
Signed-off-by: Akash Kumar <meakash7902@gmail.com>
- document `zot schema` as the generated JSON Schema reference for
configuration files
- add a top-level configuration map to help users discover the major
config sections before reading examples
Fixes#2967
Signed-off-by: Akash Kumar <meakash7902@gmail.com>
* fix(api): set blob response Content-Type from OCI descriptor
Blob HEAD responses had no Content-Type and GET responses echoed the
request's Accept header verbatim, which produced missing or malformed
media types and left multipart/byteranges parts without a per-part
Content-Type. This breaks OCI distribution-spec conformance and
consumers like stargz-snapshotter that need a well-formed layer media
type.
Add a blobResponseMediaType helper that resolves the descriptor's
MediaType via GetBlobDescriptorFromRepo and falls back to
application/octet-stream. Use it in CheckBlob (HEAD), GetBlob full
(200), GetBlob single-range (206), and per-part in
writeMultipartRanges (206 multipart). Lookup is deferred until after
the blob is known to exist.
Cover the new behaviour with mock-based unit tests in routes_test.go
and end-to-end assertions in TestPullRange.
Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
* perf(api): stream multipart blob ranges lazily with precomputed Content-Length
writeMultipartRanges previously opened every range reader up front
and emitted no Content-Length, so an N-range request held N
concurrent storage readers (and their fds / read buffers) per
response window and forced chunked encoding on HTTP/1.1 — neither
friendly to proxies nor to fan-out scenarios like stargz lazy pulls.
Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
---------
Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
* fix(sync): apply tag filters before destination mapping
Signed-off-by: Akash Kumar <meakash7902@gmail.com>
* fix(sync): return stable pointer from getContentByUpstreamRepo
Iterate by index and return &cm.contents[i] so callers get the slice
element rather than a copy of the loop variable, matching the existing
GetContentByLocalRepo helper.
Signed-off-by: Akash Kumar <meakash7902@gmail.com>
---------
Signed-off-by: Akash Kumar <meakash7902@gmail.com>
* fix(api): support multipart range blob pulls
Signed-off-by: Akash Kumar <meakash7902@gmail.com>
* fix(api): tighten multipart range response
- Drop the redundant deferred closeRangeReaders; the deferred cleanup
registered when the slice is created already covers all paths.
- Stop copying the request Accept header into each multipart part's
Content-Type. Accept can be a list of media ranges (e.g.
"application/octet-stream,*/*"), which is not a valid Content-Type and
may confuse multipart parsers. RFC 9110 lets us omit it entirely.
- Set Docker-Content-Digest on the partial-content response so range
pulls expose the same header as a full GET.
- Drop the over-broad build tag on routes_internal_test.go; the parser
unit test does not need any extension build tags.
Signed-off-by: Akash Kumar <meakash7902@gmail.com>
---------
Signed-off-by: Akash Kumar <meakash7902@gmail.com>
POST /zot/auth/logout now returns an endSessionUrl in the JSON
response body when the session was established via an OIDC provider
whose discovery document advertises an endSessionEndpoint, so the
UI can navigate the browser to it and terminate the session at the
IdP in addition to clearing the local cookie.
- The OIDC callback records the provider name in the session after
login; the github OAuth2 path is untouched.
- end_session_endpoint is read from the zitadel/oidc RelyingParty
and validated as an absolute http(s) URL.
- post_logout_redirect_uri prefers http.externalUrl when set and
falls back to deriving the origin from the incoming request.
- No id_token_hint is sent; client_id identifies the RP, so the
ID token does not need to be persisted.
- Non-OIDC sessions (local/basic/LDAP/GitHub) retain the existing
200 OK, no body behavior.
Operators must register the URI zot sends as a valid post-logout
redirect URI on the IdP client.
Ref: https://openid.net/specs/openid-connect-rpinitiated-1_0.html
Signed-off-by: Nikita Vakula <programmistov.programmist@gmail.com>
Docker Compose and Buildx proxy through the Docker daemon, which sends
a User-Agent starting with "docker/<version>" rather than the
"Docker-Client/<version>" string sent by direct Docker CLI pulls.
This caused compose/buildx pulls to skip the 401 challenge on
registries with mixed anonymous/authenticated access policies,
resulting in 'unauthorized' errors.
Add strings.HasPrefix(ua, "docker/") alongside the existing
Docker-Client check so daemon-proxied requests from any upstream
tool (compose, buildx, etc.) are handled correctly.
Fixes#3991
feat(zb): support for measuring TTFB + sync tests
Adds supports for measuring time to first byte (TTFB)
for Pull tests for the manifest check, the manifest get,
the config get, and the blob get.
Additionally, this introduces 2 new sync tests which measure
the performance of on-demand sync.
Setup code has been refactored to accomodate a new
blob size of 1GB.
Parts of zb have been refactored to address linter errors.
Signed-off-by: Vishwas Rajashekar <dev@vrajashkr.com>
Align closing blob upload (PUT) with the OCI Distribution Spec: invalid /
out-of-order upload ranges (ErrBadUploadRange) return 416 Requested Range Not
Satisfiable instead of 400, for both the final-chunk PutBlobChunk path and
FinishBlobUpload.
GetBlobUpload (GET upload status): fix the Range response when zero bytes have
been received—send Range: 0-0 instead of Range: 0--1, consistent with a new
session and the spec’s Location + Range upload status shape. Only map
ErrBadBlobDigest to 400 here; do not handle ErrBadUploadRange on GET (that
request carries no range; ImageStore.GetBlobUpload does not return it).
Document PUT upload failures 400 and 416 in swagger; regenerate swagger
artifacts. Update route tests (expect 416 on UpdateBlobUpload for
ErrBadUploadRange), drop the mock-only GetBlobUpload+ErrBadUploadRange case,
and assert Range: 0-0 in TestPullRange after GET on a new upload location.
Fix potential panic when parsing Content-Range (index out of range)
when accessing `tokens[0]`.
Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
* fix(security): remove InsecureSkipVerify from metrics client (TLS-1)
Replace the unconditional InsecureSkipVerify: true TLS config in
newHTTPMetricsClient with the system cert pool (+ TLS 1.2 minimum).
Add an optional CACert field to MetricsConfig and to the exporter
ServerConfig so operators running zot with a self-signed or private
CA can point the exporter at the correct CA file instead of
disabling certificate verification entirely.
Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com>
* feat(metrics): add HTTPS configuration for metrics exporter
Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com>
* fix(security): enhance CA certificate handling in metrics client and add tests
Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com>
* fix(security): improve CA certificate error handling in metrics client and update tests
Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com>
* fix(tests): correct package name in minimal_client_test.go and simplify error declaration
Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com>
* fix(tests): update package name in minimal_client_test.go for consistency
Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com>
---------
Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com>
fix(security): suppress Allow-Credentials on wildcard CORS origin (CORS-1)
Per CORS spec §3.2, Access-Control-Allow-Credentials must not be
"true" when Access-Control-Allow-Origin is the wildcard "*".
ACHeadersMiddleware (pkg/common/http_server.go) and
getUIHeadersHandler (pkg/api/routes.go) now only emit the
credentials header when an explicit, non-empty AllowOrigin is
configured. Deployments that leave AllowOrigin blank (default
wildcard) no longer produce a contradictory header pair.
Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com>
Wrap req.Body with http.MaxBytesReader before io.ReadAll in
CreateAPIKey. Requests with bodies larger than MaxAPIKeyBodySize
(4 KiB) now return HTTP 413 instead of buffering arbitrary data.
Add the MaxAPIKeyBodySize constant, update the Swagger @Failure
annotation to document 413, and add a unit test.
Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com>