Commit Graph
7803 Commits
Author SHA1 Message Date
ZverGuy f775948d2c test(observability-proxy): replay the early line into the e2e tail
The background tail goes through the console route and the proxy's
reviews before it subscribes, so the early ingest can land first, and
a tail without start_offset returns only lines written after it
starts. The tail now replays the last minute, which still leaves the
hold and the late-line check meaningful.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-10 00:45:06 +07:00
ZverGuy b1108e836a fix(observability-proxy): refuse to resolve a store before the cache syncs
An absent namespace resolves to the root store, and before the
informer's first list every namespace is absent, so a request served
in that window read a labeled namespace's logs from the wrong store.
Readiness keeps the Service from routing there, but a client that
addresses the pod directly is not stopped by it.

The resolver now fails while the cache is unsynced, which answers
503 like any other resolution error.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-10 00:45:06 +07:00
ZverGuy cd04c3e6c4 fix(observability-proxy): count the aligned buckets in the logs step
vlselect widens the window to step boundaries and returns a
zero-filled bucket for each one, so a start off a boundary yields one
bucket more than the span divided by the step. Sizing the step from
the unaligned span let hits and stats_query_range return MaxBuckets+1
buckets.

The minimum step now spans MaxBuckets-1 steps, and the smallest
accepted --logs-max-buckets rises to 4 so that this step still fits
the third-of-range cap that keeps alignment inside the window.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-10 00:45:06 +07:00
ZverGuy 2d8de08724 fix(observability-proxy): refuse positional arguments
The flag parser stops at the first argument that is not a flag, so
a stray word in the container args made every flag after it fall back
to its default without an error, the root tenant included.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-10 00:45:06 +07:00
ZverGuy 2e3edcf30d fix(observability-proxy): refuse a non-finite per-user rate
NaN fails the negative check, as every comparison does, and +Inf
refills a bucket instantly, so either value switched the per-user rate
limit off while the flag looked set. Both now stop the proxy at
startup.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-10 00:45:06 +07:00
ZverGuy d3e6fd6aa8 fix(observability-proxy): keep the rate-limit buckets under their bound
When the bucket map was full and the sweep freed nothing, the new
user's bucket was added anyway, so the map grew past its bound and
every further newcomer paid a full scan under the limiter lock.

A newcomer is now refused with 429 until the first bucket refills,
and the Retry-After tells it when. Resetting the map instead would
hand every throttled user a fresh burst.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-10 00:45:06 +07:00
ZverGuy bb073eec1b fix(observability-proxy): do not cache reviews that failed to decide
A TokenReview whose authenticator failed comes back unauthenticated
with status.error set, and a SubjectAccessReview whose authorizer
failed comes back with an evaluation error and no decision. Both were
cached as refusals, so a valid caller stayed locked out for the cache
TTL after the outage ended.

Such answers are now refused for the current request only. A review
that failed to decide still answers 401 or 403 rather than 503: RBAC
sets an evaluation error for any binding to a missing role, which a
retryable status would turn into a permanent 503 for that user.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-10 00:45:06 +07:00
ZverGuy 0dc8a49c19 fix(observability-proxy): cut the connection when a log answer breaks off
Once the status line has gone out, a read error from the store left
the handler returning normally, so the chunked body ended cleanly and
the client took a partial answer for a complete one.

The handler now aborts the response in that case. The request is
counted before the abort, because the abort skips the rest of the
handler.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-10 00:45:06 +07:00
ZverGuy 8b8b81013c fix(observability-proxy): bound the wait for tail response headers
The tail idle timer started only once the store had sent its headers,
and a tail request carries no deadline, so a store that accepted the
request and stayed silent held the tail slot until the client left.

The timer now starts before the request, and the same timer bounds the
header wait and the gap between lines. A store that never answers
gets 504, like a non-tail request past its deadline.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-10 00:45:06 +07:00
ZverGuy ddc37f9c4f fix(observability-proxy): put a deadline on writes to the client
A client that stopped reading kept its slot for good: the first flush
ran before the tail idle timer started, and cancelling the upstream
request does not interrupt a write that is blocked on the client.

Every response now carries a write deadline. A tail renews it by the
idle timeout before each line, so a tail that keeps receiving stays
open while a stalled reader is cut and frees its slot; any other
response must be written by the request deadline.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-10 00:45:06 +07:00
ZverGuy f81aaf4d25 fix(observability-proxy): admit a request before reading its body
The form was parsed before the concurrency slot was taken, and the
listener sets no body-read deadline, so a client that never finished a
POST body held a handler outside every limit for as long as it liked.
Tenant workloads reach the Service directly, so nginx buffering does
not cover this path.

The slot is now taken first and the body is read under the request
timeout; a body that does not arrive in time answers 408 and frees the
slot. The deadline is lifted once the body is read, because the
server's background read would take a later expiry for a client that
went away and cancel a live tail.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-10 00:45:06 +07:00
ZverGuy 4101dac9b2 test(observability-proxy): pin gzip on non-tail log requests
Only tails opt out of the transport's gzip negotiation; bounded
requests keep it on purpose. Assert that side too, so a change that
applies identity to every request fails a test instead of silently
dropping compression between the proxy and the store.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-10 00:45:06 +07:00
ZverGuy e6e3466b7d fix(observability-proxy): open live tails without waiting for a line
Go's transport asks for gzip on its own when a request sets no
Accept-Encoding. vlselect answers a gzip-negotiated tail through a gzip
writer that holds even the response headers until the first line, so the
proxy could not forward its 200 and the console kept showing a
connecting state for quiet applications; buffered lines were also
delayed.

The tail request now sends Accept-Encoding: identity, which also turns
off the transport's transparent gzip. Other endpoints keep it: they are
bounded responses read to the end, compression saves bandwidth between
the proxy and the store, and the transport decompresses before the body
is copied to the client.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-10 00:45:06 +07:00
ZverGuy f19363ec62 fix(observability-proxy): keep Retry-After positive at tiny rates
The wait until the next token was converted from float to Duration
without a range check, so a rate limit below about 1e-10 requests per
second produced a negative Retry-After on amd64. The wait now saturates
at the largest Duration.

The earlier 429 change said range, step, bucket and limit violations
keep 422; on the logs root they are clamped or answer 400, and nothing
answers 422.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-10 00:45:06 +07:00
ZverGuy 97a536b89a fix(observability-proxy): refuse durations past the int64 range
parseDuration summed duration parts without an overflow check and
converted out-of-range floats to Duration, which Go leaves
implementation-defined. A start_offset such as
99999999999h99999999999h1s wrapped to 999ms and was accepted, and a
huge step saturated on some architectures and wrapped on others. Such
values now answer 400 like any other invalid duration.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-10 00:45:06 +07:00
ZverGuy 889712d244 fix(observability-proxy): grant the proxy only the API calls it makes
Nothing the proxy does reads ApplicationDefinitions, so the rule for
them is dropped. Namespaces are read through an informer, which only
lists and watches, so get is dropped as well.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-10 00:45:06 +07:00
ZverGuy 163b079845 fix(observability-proxy): answer 429 to rate and concurrency limits
Clients such as Grafana and browser retry logic back off on 429 Too
Many Requests, not on 422, which they treat as a final rejection of
the query. The per-user rate limit, the per-user concurrency limit and
the open-tail caps are about the caller's pace, not the query, so they
now answer 429. The rate limiter knows when its bucket refills, so that
answer carries Retry-After; the concurrency caps cannot know when a
slot frees and omit it. Range, step, bucket and limit violations keep
422.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy 8784863632 test(observability-proxy): wait for every seeded line before checking scope
The suite checked that B's line and the account-7 line are absent from
A's answers but only waited for A's line to become searchable, so an
absence could come from a line the store had not indexed yet. The step
budgets in the comments now count the calls the steps make.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy dd96a415a9 test(observability-proxy): make every bucket-cap case need the rounding
Two of the three windows divided into whole milliseconds, so they
passed with the old truncating step and guarded nothing. Each window
now leaves a sub-millisecond step remainder.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy ebb5a9cec4 test(observability-proxy): cover the namespace logs scope end to end
Unit tests mock the API server and the store, so nothing shows that the
console's nginx, the proxy's TokenReview and SubjectAccessReview and a
real VictoriaLogs agree. The suite drives a viewer of one tenant through
the console against a seeded store: it reads its own namespace, is
refused a sibling's, cannot widen the scope with client filters, an
AccountID header or dot segments, and keeps a tail open past a minute.

The root store is off in e2e, so the suite runs its own VictoriaLogs
under the root store's Service name rather than waiting on Fluent Bit.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy 28b391ff17 fix(observability-proxy): round the step up to whole milliseconds
The step is sent to vlselect in milliseconds and was truncated, so a
window that the bucket cap does not divide evenly got one bucket more
than --logs-max-buckets allows. Round it up before the window is
shrunk to fit the step, so both caps still hold.

Bare-seconds steps and start offsets are now tested on the accepting
side too: only the refused forms were, and disabling the digit branch
passed the suite.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy a56ff5050f test(observability-proxy): accept the smallest satisfiable step cap
Startup refuses --logs-max-buckets below 3 and --logs-max-range below
3s, but only the refusals were tested, so moving either bound up by
one went unnoticed. Pin the smallest accepted pair.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy d452b30e42 test(dashboard): name the templated nginx config in the buffer check
The console nginx config now ships as an entrypoint template, and a
failure message naming the old conf.d path sends the reader to a file
the Containerfile no longer writes.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy 74a7a44423 feat(dashboard): route the observability proxy roots from the console
The logs and metrics roots under /apis/cozystack.io and
/apis/apps.cozystack.io are served by the observability proxy, not by
the API server, so the console nginx sends them there ahead of its
/apis prefix location, unbuffered and with the same long timeouts, so
that a live tail reaches the browser as it is written.

nginx resolves a literal upstream once at startup and refuses to start
when the name does not resolve, which would take the console down on
every cluster that does not run the proxy. The upstream is therefore a
variable resolved per request, and such a cluster answers 502 on those
paths only. That resolver does not apply the pod's search domains, so
the config becomes a template the image's entrypoint renders with the
pod's nameservers and the platform cluster domain, which the chart now
passes to the console.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy f7719cf6e2 feat(tenant): allow egress to the observability proxy
A tenant's Grafana reads the tenant's logs and metrics through the
observability proxy, and tenant egress is default-deny with one
allow-to-* policy per platform service. The rule is narrowed to the
proxy pods and their port, since the proxy's namespace holds nothing
else a tenant should reach.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy bbdf05c6c7 feat(platform): deploy the observability-proxy
The proxy only answers for stores that the monitoring stack runs, so it
follows monitoring-agents: on by default in the full variants and
opt-in through enabledPackages on the slim ones, where monitoring is
opt-in too. It is a stateless reader with no CRDs, so networking is its
only dependency.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy 7ec648d863 feat(observability-proxy): add the chart and image build
Tenants read their logs, and later their metrics, through an
authorizing proxy that sits in front of the VictoriaLogs and
VictoriaMetrics read paths, so the proxy needs a deployable package
before the console or tenant Grafana can reach it.

The proxy reviews every token and access decision against the API
server, so its own role is limited to creating TokenReviews and
SubjectAccessReviews and reading ApplicationDefinitions and namespaces.
The namespace logs root is authorized as list on cozystack.io logs,
which no API server serves; the rule is aggregated into the tenant view
role so that a namespace viewer passes that review, as upstream grants
pods/log at view.

The image ref lands without a digest until the first build stamps it,
and the package joins the root build list so every build path refreshes
it.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy ca7f283b6a fix(observability-proxy): require a range and bucket count the step cap fits
The step is capped at a third of the maximum range so that vlselect's
alignment to whole steps cannot widen the window past that range. With
fewer than three buckets, or a range under three seconds, the minimum
step exceeds that cap and the bound no longer holds, so such settings
are refused at startup.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy 4e8071aa2b fix(observability-proxy): accept bare seconds only as plain digits
Bare seconds were parsed with ParseFloat, which also takes signs,
exponents, Inf and NaN. Such a start fell through to the relative
duration form and silently moved the window, so 1.7937864e9 meant
1970 rather than an error, and NaN converted differently per CPU
architecture. Only digits with an optional fraction are taken now;
everything else is a 400.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy 8eef2fa5d2 fix(observability-proxy): reject negative limits at startup
Zero is the documented way to disable a limit; a negative value
disabled it too, silently. Refuse it so a typo in the chart cannot turn
off a per-user or total cap unnoticed.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy d1c49b38d4 fix(observability-proxy): keep step from widening the logs window
vlselect widens [start, end] outward to whole steps before scanning, so
a step of a hundred years turned the clamped week into the whole
retention. The step is now capped at the window and at a third of the
maximum range, and the window is shrunk from its start until the
widened span still fits the maximum range.

Numeric times are accepted only as unambiguous unix seconds, because
vlselect reads the unit from the digit count and a millisecond stamp or
a bare year would otherwise land in the wrong place. The standalone
ignore_global_time_filter substring check is dropped: the options block
check already covers it, and the substring only refused plain searches
for that word.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy 1c345da367 fix(observability-proxy): reject negative timeouts at startup
A negative duration silently disables the bound it configures, so a
typo in the chart would turn off the request deadline or the tail idle
close without any sign. Refuse it when the flags are parsed.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy 159f90691e fix(observability-proxy): tell a slow store apart and bound tail memory
A request that hits the proxy's own deadline was answered like an
unreachable store, so a heavy query looked like an outage; it now
answers 504, and a client that went away is no longer logged as a
store failure. A tail line is read through a fixed buffer, so a single
huge log line cannot make one stream hold unbounded memory.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy 5fc2cba2d5 fix(observability-proxy): refuse a monitoring label that is not a namespace
The label value is spliced into the vlselect host name. A value with a
dot would address a host in some other namespace instead of a tenant's
store, so it is treated as a resolution error rather than followed.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy 8fe3c222d3 fix(observability-proxy): reject non-canonical request paths
The console nginx selects its location on the normalized path but
forwards the original request URI. A path such as
namespaces/tenant-b/../tenant-a/logs/ reaches the proxy as written, so
any interpretation the proxy makes of dot segments, empty segments or
percent-encoding could make the namespace in the SubjectAccessReview
differ from the one a normalizer downstream would see. Such paths are
answered 400 instead of being interpreted.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy cbca8ac3c9 fix(observability-proxy): refuse every LogsQL options block
A substring match on ignore_global_time_filter does not see the option
once it is written as a quoted name with an escape, or split by a
comment: the LogsQL lexer unquotes option names and joins words across
a comment, so the option still applies and lifts the time window the
proxy enforces. None of the query options is needed by a tenant, so
the options keyword followed by a parenthesis is refused outright,
skipping whitespace and comments exactly as the lexer does.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy 8b0ac342b5 feat(observability-proxy): add the observability-proxy command
The deploy unit builds an image and chart around this binary, so its
flags follow the agreed contract: listen and probe addresses, cluster
domain, root tenant and auth cache TTL, plus the logs limits. Defaults
are chosen so a stock install is safe without tuning; readiness waits
for the namespace cache because store resolution reads it on every
request. On shutdown open tails are cut after a grace period so their
clients reconnect with since to another replica.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy 689405127d feat(observability-proxy): serve the namespace logs root
Tenants get a way to read their namespace's logs without Grafana and
without reaching vlselect directly, which would give them neither an
RBAC check nor a forced scope. The handler authenticates, authorizes
from the path, rebuilds the upstream request with fixed tenant headers
and no client headers, and returns VictoriaLogs' native response.

A live tail is flushed after every line, is not subject to the request
timeout, and is closed once idle, so it survives far past the apiserver
request timeout without leaving forgotten browser tabs holding streams
open. An unreachable store answers 502 so the dashboard can tell an
outage from an empty result.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy 61275df240 feat(observability-proxy): cap per-user concurrency, rate and open tails
The log stores are shared by every namespace routed to them, and the
root store also serves platform Grafana, so one tenant's dashboard must
not be able to monopolise vlselect. Live tails are counted apart from
request concurrency because they hold their slot for minutes and would
otherwise starve the same user's panels.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy eb0d6e5957 feat(observability-proxy): rebuild logs queries from an allowlist
vlselect has no notion of a tenant: whatever filter reaches it decides
what is returned. The upstream parameters are therefore rebuilt from a
per-endpoint allowlist with the namespace scope as the only
extra_stream_filters value, so a client cannot widen it through
extra_filters, tenant headers or an alternate parameter spelling.

VictoriaLogs ANDs extra_stream_filters into every subquery, so union
and in() queries pass through unparsed. What the query can still do is
cost: a missing start scans the whole retention, a tiny step returns a
bucket per millisecond, query has no default limit, and
ignore_global_time_filter lifts the window altogether. Each of those is
bounded or refused here, and a tail resume point becomes a capped
start_offset.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy e02d0fa16e feat(observability-proxy): resolve a namespace to its log store
Fluent Bit writes a namespace's logs to the store its monitoring label
names, falling back to the root tenant, so exactly one store answers for
a namespace. Reading with any other rule would show an empty log view
or the wrong tenant's store, so the resolver repeats the collector's
rule and is tested against the same namespace set as its chart test.
It reads a namespace lister, keeping store lookups off the apiserver.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy 448cd2b5bd feat(observability-proxy): authenticate and authorize with cached reviews
The proxy holds no credentials of its own: kube-apiserver answers who a
bearer token belongs to and whether that identity may read a scope, the
same way an aggregated API server would ask through delegated auth.
Both answers are cached briefly so a dashboard polling several panels
does not turn into a review per request, while transport errors are
left uncached so an apiserver blip does not lock users out for the TTL.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
ZverGuy 8e35ff521c feat(observability-proxy): parse the four tenant observability roots
The proxy derives its single SubjectAccessReview from the request path,
so the path has to be split and validated before anything else runs.
Names are DNS-1123 checked because they end up quoted inside LogsQL and
PromQL scopes, and percent-encoding is refused so a decoded slash
cannot shift the segments authorization is built from.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
2026-11-09 15:48:57 +07:00
myasnikovdaniil 4087218a02 docs: move the repo-only guides onto the website (#5034)
## What this PR does

Seven guides under `docs/` were operator facing and written alongside
the code, and left in the source tree where no user looks. They are on
the website now, so this deletes them here and repoints every reference.

```
docs/operations/backup-classes.md                  -> operations/services/backup-classes
docs/operations/resource-presets.md                -> guides/resource-management
docs/storage-immutability.md                       -> operations/storage-class-immutability
docs/operations/system-memory-limits.md            -> operations/configuration/system-memory-limits
docs/gateway-classes.md                            -> networking/gateway-classes
docs/observability/distributed-tracing.md          -> operations/services/monitoring/distributed-tracing
docs/operations/seaweedfs-capacity-for-backups.md  -> operations/services/object-storage/capacity-for-backups
```

`docs/release.md`, `docs/deprecating-applications.md` and the two
SeaweedFS runbooks in `docs/operations/` stay. A workflow, `AGENTS.md`,
a migration and a bats suite read them by path, which is the test from
#3777.

References point at `https://cozystack.io/docs/v1/...`. The site
rewrites that short prefix to the latest version in `layouts/404.html`,
so the links follow releases and pin none. Backup Classes, resource
management and `storageClass` immutability exist in the latest version
already. The other four pages are only in `next/` until 1.7.0 is
promoted, and their links start to resolve then. The generated mongodb
files come from `values.yaml` through `make generate` with
cozyvalues-gen v1.7.0, the version pinned in
`.github/workflows/pre-commit.yml`.

Tests: helm unit tests for mongodb (151), postgres (209), vm-disk (114)
and kubernetes-nodes (223), go tests for backupcontroller, presets and
the operator, rd-presets-check, the migration-50 bats suite.

### Downstream repositories

- [x] [cozystack/website](https://github.com/cozystack/website) -
follow-up: cozystack/website#763

Walked the rest of the trigger map against the diff. No provider
follow-up: the mongodb `values.schema.json` diff is one `description`
string, with no field added, removed or renamed, no enum, no default,
and no `kind`, `plural` or `release.prefix` change.
`packages/core/platform/values.yaml` changed in comments only and no
key, so the hand written platform-package table on the site is
unaffected.

Please merge after cozystack/website#763, the links in the code only
resolve once those pages deploy.

### Release note

```release-note
docs: seven guides move from the docs/ directory of this repository to cozystack.io (Backup Classes, resource presets, storageClass immutability, system component memory limits, GatewayClass selection, distributed tracing, SeaweedFS capacity for backups), and in-repo references point there.
```
2026-11-03 23:09:46 +05:00
Timofei Larkin e31f518df7 ci(api-review-gate): publish the gate as a commit status (#3440)
## What this PR does

Publishes the `API Review Gate` merge context as a commit status, so a
skipped or cancelled run cannot pass it or poison it, and gives fork PRs
the same gate through a privileged `workflow_run` workflow.

### Why

The merge context was a job name. A required job that skips counts as
passed, and check runs are not deduplicated per name, so one cancelled
run on a head keeps the context failed until the next push. Measured on
this repo: `41d1153c` has four check runs with the gate's job name (one
`cancelled`, three `success`). Commit statuses are deduplicated per
context and the newest wins, so a run with no verdict posts nothing and
the previous status stays.

### What changes

Same-repo PRs get status from `api-gate-report`. A `pending` opener
marks the head before the evaluation on approve, dismiss and
request-changes reviews, so an old `success` is not mergeable for the
minute the evaluation takes. `COMMENTED` reviews no longer count as a
reviewer's latest review, so an owner who approved and then commented
keeps the approval. Concurrency is split, so bot comment run cannot
cancel live evaluation.

Fork PRs: `api-review-gate-fork.yaml` runs from the default branch and
reaches the verdict itself, the fork's run is only a trigger. `resolve`
finds PR by head SHA in open PR list (review runs on a fork report base
repo as head repository and have no pull requests). `detect` fetches the
head as data and runs the detector in a job with `contents: read` only.
`conclude` applies the approval rule and posts. A sweep every 15 minutes
moves the status of fork PRs whose runs wait for a maintainer in
`action_required`, those never fire `workflow_run`. This is lllamnyp's
patch, applied whole, with these changes on top:

- two open fork PRs on one head fail closed in `resolve` and in the
sweep, a head carried by a same-repo PR stays with `api-gate-report`
- detector is built from default branch only, merge base is picked by PR
author and an old one would mean an old detector
- the fork `detect` step runs under `bash -e` and detector exit 2 ended
it before the code was read, so it has `set +e` like the same-repo job
- head is checked out with links as links, like the same-repo gate does,
because flattening them hid a CRD behind a file link from the detector
while Helm still ships it

### Limits

- A fork PR whose push run waits for a maintainer's approval has no
status until that run executes, same as `E2E Tests`.
- A fork PR on a head that a same-repo PR also carries shows the verdict
standing there, one commit status cannot hold a verdict per base.
- The sweep can publish a stale `success` if it reads an approval,
stalls, and the owner withdraws it meanwhile. The next sweep flips it
back, so the window is up to 15 minutes.
- Two copies of one first-party CRD and CRDs behind directory links are
gaps of the detector on main, same for same-repo PRs, they are in
https://github.com/cozystack/cozystack/issues/5170.

### Testing

`actionlint` is clean on both workflows. `zizmor` is clean too, but
`dangerous-triggers` and `template-injection` are disabled repo-wide in
`.github/zizmor.yml`, so that says little about a privileged workflow. I
ran the `github-script` bodies under node with a mocked API (every
conclusion, approval and shared head case) and the run steps under `bash
-e` with a stub detector exiting 0, 2 and 1. `make unit-tests` passes
with `LC_ALL=C` (a kafka bats test depends on the locale, nothing to do
with this PR).

Not exercised live: the fork path and the sweep run only from the
default branch, so their first run is after merge.

### Repo admin

Branch protection requires the old job name `Require API owner review
for sizeable API changes`. This PR renames that job, so after merge the
required context has to be changed to the `API Review Gate` status.

### Downstream repositories

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

### Release note

```release-note
NONE
```
2026-11-02 19:15:18 +04:00
Aleksei Sviridkin a2cc9a3016 fix(dashboard): link a tenant row to its detail page (#4004)
## What this PR does

Fixes #3822

Tenant list showed name as plain text and the only link in a row was
Edit button, so tenant detail page with its tabs and Delete was
reachable only by opening edit form and cancelling out of it. Name cell
is a link to that page now, it appears exactly where Edit appears and
points to the same CR. Plain click switches selected tenant to the row's
parent first, same as Edit does, because detail page takes namespace
from selected tenant. Ctrl, cmd and shift click don't run that switch,
so the tab you open a tenant from stays where it was.

First commit fixes what the link depends on. Console recovers a tenant's
parent from namespace name (`tenant-a-b` sits under `tenant-a`), but
root is the one level that naming skips: tenant `acme` ordered in
`tenant-root` owns `tenant-acme`, not `tenant-root-acme`. So
`realParentNamespace` returned undefined for every top level tenant,
which is most tenants on a normal cluster, and Edit button has been
missing there for as long as it exists. Ancestor labels carry the parent
that the name does not. Root level tenant named `root-x` resolves to CR
`root-x` now, not `x`.

### Notes

I first tried to make links carry their tenant in the url (`?tenant=`),
so a detail page opened in new tab resolves on its own. That turned into
questions about tenant context itself: what to do with a tenant in a
link the user can't see, what localStorage may hold, and TenantNamespace
LIST ignoring `labelSelector`. Detail route addresses a Tenant CR by
relative name and takes namespace from selected tenant, and making that
robust is a refactor of tenant context and routing, which should start
with study and planning, not with patches on patches. So this PR only
closes the UX gap (no way from tenant list into a tenant's page), going
deeper is out of scope.

Known limit that comes with it: link has no tenant in it, so opening it
in a new tab (ctrl/cmd/middle click, open in new tab, copied url)
resolves against the tenant last selected in any tab, because selection
lives in shared localStorage. For a row directly under that tenant it
opens the right page. For a row deeper in the tree the page says
`Application <name> not found.` and there is no Delete. If that tenant
also has a direct child tenant with the same relative name, new tab
opens that other tenant's page with working Edit and Delete, and delete
confirm says only `Delete Tenant "<name>"?`. Plain click in the same tab
is not affected. Detail page's own tabs and Edit link already behave
this way, it's how detail route resolves its namespace today and fixing
it properly is the refactor above.

A row links, and offers Edit, exactly when namespace holding its Tenant
CR is one the apiserver returns to this user. It returns a tenant
namespace only if the user is subject of a RoleBinding in it, so
sub-tenant admin that cant act in `tenant-root` sees no link there.

No row level Delete button. Issue allows either that or a route to
detail page, I took the route so confirm and mutation stay in one place.

Covered by tests, every source change here fails a test when reverted:

- top level tenant, nested one, root level tenant named `root-x`, forest
root without ancestor labels
- bridged row whose real parent is invisible gets neither link nor Edit
- plain click switches to the row's parent and opens detail page
- ctrl click leaves selection and location alone

### Screenshots

Tenant list under root on a dev cluster. Every row name is a link to
tenant page now (`pr4078` is hovered), top level tenants had neither
link nor Edit before. Host column redacted.

<!-- SCREENSHOT: drag in 01-tenant-list-links.png here -->
<img width="1440" height="900" alt="01-tenant-list-links"
src="https://github.com/user-attachments/assets/39cd723a-1ac5-4d3b-8c36-b70ddc626961"
/>


### Downstream repositories

<!--
Cozystack is upstream for repositories that are NOT kept in sync with it
automatically, and nothing in CI compares the two sides, so a change
here can break them silently.

Which change forces what, and which file to touch, is the trigger map in
docs/agents/contributing.md under "Downstream Repositories". Walk it
before you tick anything.

IF YOU ARE AN AI AGENT OR AN LLM, THIS IS PART OF THE TASK, NOT A FORM
TO FILL IN:

1. Walk the trigger map against the actual diff of this PR, file by
file. Do not decide from the PR title or from your memory of what you
changed.
2. For every repository you tick, open the follow-up PR there and link
it on the same line. A ticked box with no link claims work that does not
exist, which is worse than an empty box. Search that repository for an
open PR or issue covering it first, and link that instead of filing a
duplicate.
3. When the follow-up is out of scope, or needs a decision that is not
yours to make, open an issue in that repository instead, link that, and
say so in the PR body. Do not push a speculative PR into another
repository just to fill a line here.
4. Do not tick "No downstream repository is affected" to make the
checklist go away. If you are unsure, leave every box empty and say so
in the PR body, so a human decides.
-->

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

All changed files are under `packages/system/dashboard/images/console`,
console has no downstream copy (standalone UI repo is archived).

### Release note

<!--  Write a release note:
- Explain what has changed internally and for users.
- Start with the same `type(scope):` prefix as in the PR title
- Follow the guidelines at
https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md.
-->

```release-note
fix(dashboard): tenant name in the tenant list links to its detail page now (where Delete is), and top level tenants get their Edit button back.
```



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **Bug Fixes**
* Root-level tenant names are now displayed without incorrectly removing
prefixes, and tenants ordered directly under the root are correctly
recognized in the hierarchy.
* Tenant rows link to their detail pages when the parent tenant is
accessible. Same-tab navigation updates the active tenant, while opening
a link in another tab leaves the current selection unchanged.
  * Edit links and navigation now use consistent tenant routes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-11-02 16:33:51 +03:00
Myasnikov Daniil b5e3b37a9a ci(api-review-gate): build the fork detector from the default branch
The merge base is the commit the fork's author branched from, so a
branch cut from an old commit selected an old detector, and a fix to
the detector never reached the PRs an attacker controls. The workflow
itself comes from the default branch, so the detector now does too.

The head is also checked out with its links intact. Flattening them
hid a CRD behind a file link from the detector while Helm still
shipped it, and made the verdict differ from the one the same-repo
gate reaches.

Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-11-02 14:47:51 +05:00
Aleksei Sviridkin d32092ac9a feat(release): attach an SBOM to every release image (#4997)
## What this PR does

This PR attaches an SPDX SBOM to every first-party image of a release.
BuildKit adds it at build time as an attestation, so it sits in the
image's index and moves with the digest the release pins, through
promotion. `docker buildx imagetools inspect <image>@<digest> --format
'{{ json .SBOM }}'` prints it, one document per platform. Releases cut
from this change on have it. The Talos installer image (built by the
imager, pushed by skopeo) and the third-party images don't.

It is more than `SBOM: '1'` in `tags.yaml`, because three things broke
on the way:

- An image built with an attestation is an index of the image and an
attestation manifest. `image_arch` in `hack/stitch-multiarch.sh` read
that as not one amd64 and one arm64 image, so the stitch skipped every
such image and still exited 0, and `hack/verify-multiarch.sh` then
failed the release with "index without linux/arm64". `image_arch` and
the rerun check look through the index now, and the stitched index keeps
both attestations.
- The release `make build` ran on the default docker driver, which
cannot attach attestations. It runs on the docker-container builder with
the docker.io mirror now, like the arm64 leg. `use: false` stays, so a
plain `docker build` keeps the default driver.
- `--sbom=true` pulls `docker/buildkit-syft-scanner:stable-1`, a
floating tag. `SBOM=1` passes `--attest type=sbom,generator=` with the
scanner pinned by digest (1.12.0). The comment in `hack/common-envs.mk`
called the output CycloneDX, BuildKit writes SPDX, and the same word in
`docs/security/self-assessment.md` is fixed too.

Only the release lane changes. PR, main and nightly builds push to OCIR
and are not what users install, so the OCIR attestation probe the
tracker (A2 in #3278) listed is not on this path, release images go to
GHCR.

### How it was checked

- Bats 1.14.0: four new tests in `hack/stitch-multiarch_test.bats` (the
arm64 leg's workflow shape, a stitch of SBOM images, a rerun after one,
an index of two linux images that is skipped), two in
`hack/common-envs_test.bats` (the flags, and the Renovate manager held
to the `SBOM_SCANNER` line), and the matchbox workflow test rewritten
for the new builder. The stitch tests fail on `main`'s
`stitch-multiarch.sh`, and each of these mutations fails one: dropping
the `length == 1` check, counting the attestation as a platform, looking
for the amd64 index itself in the rerun, taking `SBOM`, `BUILDER` or the
mirror config out of `tags.yaml`, going back to `--sbom=true`, dropping
the scanner digest, turning `SBOM` on by default, reformatting the
`SBOM_SCANNER` line out of the Renovate manager's regexp.
- A rehearsal on a localhost registry (docker 29.8, buildx 0.37.1) with
the real scripts. Images built through the `BUILDX_ARGS` of
`common-envs.mk` for amd64 and arm64, one without SBOM beside them, then
`stitch-multiarch.sh` (2 stitched, 0 skipped), `verify-multiarch.sh` (0
failed), `imagetools inspect` reading SPDX 2.3 with 15 packages per
platform from the stitched index, and `skopeo copy --multi-arch all`
keeping the index digest. `containerimage.digest` in `--metadata-file`
equals the pushed index digest, so the digest the package Makefiles
stamp is the index. The same run with `main`'s stitch skips the SBOM
image, exits 0 and fails the gate.
- `make bats-unit-tests`: 2444 of 2445. The one failure, "all-topics
backup skips double-underscore internals and keeps single-underscore
user topics" in `hack/kafka-topic-backup-strategy.bats`, also fails on
unmodified `main` on the same machine. `make bats-posix-compat-tests`
passes, and the stitch test file passes under dash through
`cozytest.sh`. `hack/check-commit-trailers.sh` passes. zizmor and
actionlint aren't installed here, so the workflow change (two env keys
and one renamed builder step) is covered only by the tests above.
- Not checked: that GHCR takes the attestations, what retention does to
the child manifests, the fork lane's OCI export (it doesn't set `SBOM`),
and a real release build. The first alpha cut after the merge tests the
first and the last. Renovate itself did not run on the new manager: the
config passes the Renovate JSON schema, and the regexp takes the tag and
the digest off the live line, which a test holds.

### Decisions for the release owners

- Scope is `tags.yaml` only. SBOMs on main and nightly images would need
OCIR to take attestations (not probed) and would change what the nightly
mirror copies.
- The scanner is 1.12.0 with syft 1.51.0 inside, which is in the range
of GHSA-mw2c-m758-9v5q (memory exhaustion parsing binary inputs, fixed
in syft 1.52.0). It reads images our own builds produce, so I didn't
hold the change for it. A Renovate manager on the `SBOM_SCANNER` line
moves the version and the digest together when the scanner ships a newer
syft.
- The scanner adds a step per image and I didn't time it on the release
runners.
- A stitch rerun reads the amd64 index the tree pins, which stays in the
registry untagged after a stitch. If the registry drops untagged
manifests before the rerun, it fails at that read rather than repinning.
- After the first alpha with attestations I'll move A2b in #3278 and the
SBOM item in #2937 to done. The SBOM row in
`docs/security/self-assessment.md` still says SBOMs aren't published,
and stays until that alpha shows they are.

### Screenshots

<!-- REQUIRED for UI changes: attach screenshots or screen recordings
demonstrating
the visual impact of your changes. PRs with UI changes without
screenshots will not be merged. -->

### Downstream repositories

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

I walked the trigger map against the diff. It touches `tags.yaml`,
`hack/common-envs.mk`, the stitch and verify scripts and three docs. No
release asset is renamed, `hack/package.mk` and the commit convention
are untouched, and the flow a maintainer follows to cut and promote a
release is the same.

### Release note

```release-note
feat(release): every first-party container image of a release carries an SPDX SBOM as a BuildKit attestation in its index, readable with `docker buildx imagetools inspect <image>@<digest> --format '{{ json .SBOM }}'`. Releases cut from this change on have it, the Talos installer image and third-party images don't.
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

- **New Features**
- Supported release image builds now include SPDX-format software bills
of materials (SBOMs) as attestations.
- Multi-architecture image stitching, retagging, and mirroring preserve
SBOM attestations.
- **Documentation**
- Updated image-reference guidance to explain how to handle attestation
entries in multi-architecture indexes.
- Clarified which images include SBOMs, how to inspect them, and that
SBOM generation remains disabled by default outside configured release
builds.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-11-01 22:35:38 +03:00
IvanHunters 94ada11eef fix(marketplace): pin a tapped repository to a digest instead of a mutable tag (#5082)
## What this PR does

A tapped (community-connected) repository followed a mutable OCI tag:
the Flux `OCIRepository` carried only `spec.ref.tag` with a 5-minute
interval, so whoever controlled the tag controlled what ran in the
management cluster within that interval, down to the HelmReleases of
already-running tenant application instances. Fixes #4979.

A tap is now pinned to a digest. `cozypkg tap` and the connect API
accept an explicit `oci://host/org/repo@sha256:...` reference; a tag
reference is resolved and pinned to its digest by the tap materializer
once the artifact for the current spec generation is ready, from the
digest Flux reports in `status.artifact.revision`, with the tag kept
informational. A tap connected before this change is pinned to its
current digest on the first reconcile after upgrade, and a re-connect to
a new tag waits for source-controller to re-resolve before pinning, so a
stale revision is never pinned. Moving the tag upstream no longer
changes anything in the cluster; an explicit re-connect moves the pin.

Connect also accepts an optional keyless cosign verification policy
(`--certificate-identity` / `--certificate-oidc-issuer` on the CLI,
`spec.verify` on the API), anchored to an exact identity so a
superstring cannot match, and preserves it, and the pull secret, across
repeat connects rather than silently dropping them. With those flags the
CLI verifies the signature at tap time and pins the verified digest
before pulling, so the content it applies is what a trusted identity
signed; it fails closed if the verified digest cannot be determined.

### Screenshots

Not applicable: no UI changes.

### Downstream repositories

I walked the trigger map against the diff. The change is confined to
`cozypkg`, the `Tap` aggregated API (a virtual, cluster-scoped resource
with no CRD), the tap materializer, and the community-index docs. It
does not touch `packages/apps`, `packages/core/platform/values.yaml`,
installer values, platform variants/components, release assets,
`ApplicationDefinition` semantics, the `hack/` make targets, or the
commit convention, so the provider, ansible, ccp and external-apps paths
are not reached.

One path is arguable and needs a human decision, so I have left every
box unticked rather than tick or dismiss it:

- `cozystack/website` — `cozypkg tap` gains flags and digest-pinning
behaviour. The marketplace tap flow is new and I am not sure it has a
reference page on the site yet, so whether a docs follow-up is warranted
(and where) is a maintainer call.

- [ ] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

### Release note

```release-note
fix(marketplace): a tapped repository is pinned to a digest instead of following a mutable tag, so moving the tag upstream no longer changes what runs in the cluster; connect accepts oci://...@sha256:... and an optional keyless cosign verify policy (anchored to an exact identity and preserved across re-connects), and with verify flags the CLI verifies the signature and pins the verified digest at tap time.
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

- **New Features**
- OCI repositories can be connected using a SHA-256 digest, with or
without a tag. Tag-based sources are pinned to the resolved artifact
digest.
- Optional Cosign keyless verification is available during connection
using certificate identity and OIDC issuer details.
- Reconnecting preserves existing verification and pull-credential
settings when replacement values aren’t provided.
- **Bug Fixes**
- Reconnecting with a new tag clears previous operator-managed pins so
the requested tag can resolve to its current artifact.
- **Documentation**
- Clarified tag pinning, signature verification, and where tapped app
charts run.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-10-30 18:09:34 +03:00
IvanHunters ccc216652e fix(monitoring-agents)!: isolate merged log payload under log_processed (#5167)
## What this PR does

Fluent Bit's `kubernetes` filter runs with `Merge_Log On` but without
`Merge_Log_Key`, so a parsed JSON log line is merged into the record
root.
An application that logs JSON can thereby set top-level fields that
later
pipeline stages trust for routing and tenant isolation:
`kubernetes_namespace_name`
(rewrite_tag routing and the `_stream_fields` stream identity),
`kubernetes.labels`, and the `tenant` / `cluster` / `log_source`
markers.
A tenant workload can forge another tenant's values on its own log
lines.

Setting `Merge_Log_Key log_processed` nests the parsed payload under a
dedicated key, so application log content can no longer override the
top-level service fields.

The change is applied to both Fluent Bit configurations that carry this
filter: the platform agent
(`packages/system/monitoring-agents/values.yaml`)
and the guest-cluster agent (`packages/apps/kubernetes`).

### Screenshots

Not a UI change.

### Downstream repositories

Walked the trigger map in `docs/agents/contributing.md` against the
diff,
file by file. The change touches only the Fluent Bit filter config
inside
`monitoring-agents`; it does not alter package layout, any
`values.schema.json`,
platform values keys, `ApplicationDefinition`, any CRD, `hack/`,
namespaces,
telemetry metric names, or the commit convention. None of the listed
couplings match.

- [x] No downstream repository is affected by this change

### Release note

```release-note
fix(monitoring-agents)!: Fluent Bit now nests parsed JSON log content under a `log_processed` key instead of merging it into the record root. This prevents an application log line from forging the service fields used for log routing and tenant isolation. BREAKING: top-level fields previously extracted from JSON logs (for example `level`, `msg`, and any application-specific keys) now live under `log_processed.*`; external dashboards and LogsQL queries that read them from the record root must be updated.
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **Improvements**
* Merged Kubernetes log content is now grouped under `log_processed` in
Fluent Bit output.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-10-30 17:14:45 +03:00