Repository navigation
Releases: querdian-dev/querdian
Release list
v0.1.2
Changelog
Bug fixes
- fd14805: fix(auth): log failed OIDC issuer discovery attempts (@ralf157)
- 61ba570: fix(deps): update go modules (minor/patch) (@renovate[bot])
- 67782b5: fix(deps): update golang.org/x/net to v0.62.0 (@geilername)
Verify
cosign verify ghcr.io/querdian-dev/querdian:0.1.2 \
--certificate-identity-regexp '^https://github.com/querdian-dev/querdian/\.github/workflows/release\.yml@refs/tags/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comv0.1.1
A security release. Upgrade if you proxy Tempo: in 0.1.0, Tempo tag lookups could list tag names and values of spans outside a user's policy (GHSA-jcqw-3c3f-xrmj). Prometheus and Loki backends are not affected. The API (policy.querdian.dev/v1alpha1), the configuration and the Helm values are unchanged.
Security
Querdian enforced policies on Tempo's api/v2/search/tags and api/v2/search/tag/<name>/values by injecting the policy into q. Tempo does not evaluate every q on these endpoints: for a filter shape it cannot use there it answers unfiltered, and the injected policy is dropped with the rest of q (black-box: Tempo 2.9.0 and 2.10.10 for terms joined by ||, Tempo 3.1.1 for =~). Affected were restricted users whose policy has rules on different attributes (Tempo 2.x) or a regex, including several = rules on one attribute (Tempo 3.x). Search, trace by ID, TraceQL metrics and tenant isolation were not affected.
0.1.1 sends restricted tag lookups per policy term, each as one filter of attribute = "string" conditions, and unites the answers. This changes behaviour for some users:
- Policies with
!=,!~or an open regex (ns-.*) get400 unsupportedon the two tag endpoints; Grafana's TraceQL autocomplete then offers no values for them. Search and metrics work as before. - A user
qon these endpoints is honoured only when it is a conjunction ofspan.x = "v",resource.x = "v"andstatus = ...conditions; otherwise it is ignored and the answer lists the tags or values of all granted spans. - A policy with several terms causes one upstream request per term, capped by
limits.maxFanout(default 16).
Details are in limitations.md. The fix was verified with the Tempo end-to-end suite against Tempo 2.9.0, 2.10.10 and 3.1.1.
If you cannot upgrade, deny api/v2/search/tags and api/v2/search/tag/* to restricted users in front of Querdian, or use only single-term policies whose matchers are all = (on Tempo 3.x also with a single value per attribute).
Other changes
- The end-to-end suites and the quickstart run Prometheus v3.15.1, Loki 3.7.9 and Tempo 3.1.1 (0.1.0 was tested with v3.5.0, 3.5.0 and 2.9.0). Apart from the tag-lookup fix above, Tempo 3 needed no change in Querdian; the quickstart runs it in monolithic mode (live-store, no Kafka), with Grafana 13.2.4.
- Dependency updates, see the CHANGELOG: the distroless base image, Go modules (
prometheus/commonv0.72.1,prometheus/prometheusv0.315.1), release tooling and GitHub Actions.
Artefacts
| Container image | ghcr.io/querdian-dev/querdian:0.1.1 (linux/amd64, linux/arm64; distroless, non-root) |
| Helm chart | oci://ghcr.io/querdian-dev/charts/querdian, version 0.1.1, also on Artifact Hub |
| Binaries | querdian_0.1.1_linux_{amd64,arm64}.tar.gz with SPDX SBOMs, on the GitHub release |
Upgrading from 0.1.0
helm upgrade querdian oci://ghcr.io/querdian-dev/charts/querdian --version 0.1.1 \
--namespace querdian -f my-values.yamlThe CRDs are unchanged. No configuration change is needed.
Verify
IDENTITY='^https://github.com/querdian-dev/querdian/\.github/workflows/release\.yml@refs/tags/'
ISSUER=https://token.actions.githubusercontent.com
cosign verify ghcr.io/querdian-dev/querdian:0.1.1 \
--certificate-identity-regexp "$IDENTITY" --certificate-oidc-issuer "$ISSUER"
cosign verify ghcr.io/querdian-dev/charts/querdian:0.1.1 \
--certificate-identity-regexp "$IDENTITY" --certificate-oidc-issuer "$ISSUER"
gh attestation verify oci://ghcr.io/querdian-dev/querdian:0.1.1 --repo querdian-dev/querdianAll commands, including the archives, are in releasing.md.
Commits since v0.1.1-rc.2
Bug fixes
- 7b93c64: fix(release): pass the pushed tag to GoReleaser (@geilername)
v0.1.1-rc.2
Changelog
Bug fixes
- 7b93c64: fix(release): pass the pushed tag to GoReleaser (@geilername)
Verify
cosign verify ghcr.io/querdian-dev/querdian:0.1.1-rc.2 \
--certificate-identity-regexp '^https://github.com/querdian-dev/querdian/\.github/workflows/release\.yml@refs/tags/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comv0.1.1-rc.1
Changelog
Bug fixes
- 4e67100: fix(deps): update go modules (minor/patch) (@renovate[bot])
- 2a05823: fix(quickstart): run the Tempo 3 live-store with the history slack (@geilername)
- fe68a95: fix(renovate): track e2e test images and quickstart manifests (@geilername)
- 0ad76d8: fix(tempo): plan tag lookups in a q shape Tempo evaluates (GHSA-jcqw-3c3f-xrmj) (@geilername)
Verify
cosign verify ghcr.io/querdian-dev/querdian:0.1.1-rc.1 \
--certificate-identity-regexp '^https://github.com/querdian-dev/querdian/\.github/workflows/release\.yml@refs/tags/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comv0.1.0
Querdian is an Apache-2.0, Kubernetes-native reverse proxy that enforces label-based access control on read queries against Prometheus/Thanos (PromQL), Loki (LogQL) and Tempo (TraceQL). Policies are Kubernetes CRDs, queries are rewritten with parsers, and every error fails closed.
This is the first release. It is an alpha release with a v1alpha1 API: the CRDs may change incompatibly, and the project has no production adopters yet. Treat it as something to evaluate and give feedback on, not as a drop-in security boundary you have not tested in your own environment.
Highlights
ClusterAccessPolicyandAccessPolicyCRDs (policy.querdian.dev/v1alpha1) with status conditions; a user's access is the exact union of every policy that matches their username or groups, or the request is rejected with a reason.- OIDC bearer-token authentication; default deny for unknown endpoints, methods, parameters and users.
- PromQL, LogQL and TraceQL enforcement, including multi-tenant (
X-Scope-OrgID) backends and Lokitail. The LogQL and TraceQL parsers are clean-room implementations from the public documentation (pkg/logql,pkg/traceql). labels.denyhides labels; the opt-inlabels.redactalso rewrites their values in query results.- Tempo search responses are redacted and trace-by-ID can be filtered by policy.
- An optional, read-only policy dashboard with a query test bench (off by default).
- Helm chart (with optional NetworkPolicies protecting the backends), distroless multi-arch image, Grafana dashboard, metrics, tracing and a kind quickstart.
- Release artefacts are signed with cosign (keyless) and carry SBOMs and GitHub build-provenance attestations; see "Verify" below.
Full list: CHANGELOG.md.
Artefacts
| Container image | ghcr.io/querdian-dev/querdian:0.1.0 (linux/amd64, linux/arm64; distroless, non-root) |
| Helm chart | oci://ghcr.io/querdian-dev/charts/querdian, version 0.1.0, also on Artifact Hub |
| Binaries | querdian_0.1.0_linux_{amd64,arm64}.tar.gz with SPDX SBOMs, on the GitHub release |
Install
helm install querdian oci://ghcr.io/querdian-dev/charts/querdian --version 0.1.0 \
--namespace querdian --create-namespace -f my-values.yamlYou must set config.auth and config.backends. Start with the quickstart and the policy guide. Helm installs the CRDs on first install only; apply crds/ yourself on upgrades. To pin the image by digest, set image.digest (it takes precedence over image.tag).
Verify
The image, the chart and the checksums file are signed by the release workflow of this repository:
IDENTITY='^https://github.com/querdian-dev/querdian/\.github/workflows/release\.yml@refs/tags/'
ISSUER=https://token.actions.githubusercontent.com
cosign verify ghcr.io/querdian-dev/querdian:0.1.0 \
--certificate-identity-regexp "$IDENTITY" --certificate-oidc-issuer "$ISSUER"
cosign verify ghcr.io/querdian-dev/charts/querdian:0.1.0 \
--certificate-identity-regexp "$IDENTITY" --certificate-oidc-issuer "$ISSUER"
gh attestation verify oci://ghcr.io/querdian-dev/querdian:0.1.0 --repo querdian-dev/querdianThe checksums file is signed as a Sigstore bundle (cosign verify-blob --bundle, cosign v3 or later). All commands, including the archives, are in releasing.md.
Read this before you depend on it
The complete list is limitations.md; the attacker model is in the threat model. The ones that matter most:
- Namespace-label integrity.
AccessPolicyisolation is only as strong as the integrity of the namespace label written by your collection pipeline. If workloads can set it, they can place data in another team's view. Querdian cannot detect that. - Strict by design. Queries Querdian cannot prove it handles exactly as the backend would are rejected (
400/403). Examples: PromQL range vectors that are not the direct argument of a function when a policy has several terms; LogQL range aggregations withby/withoutunder several terms; unsupported LogQL and TraceQL syntax; TraceQL comments andparent.*scopes; trace-level TraceQL intrinsics (rootName,traceDuration,compare()) for users whose policy does not cover the root span. - Loki under tenant-bound policies (tested with Loki 3.5.0). When the rules differ per tenant, the metadata endpoints
labels,label/<name>/values,series,index/statsandindex/volumedo not work reliably (empty or incomplete results, never more than the policy allows). Multi-tenant grants do not work fortail,seriesanddetected_labels. A proper fix needs one request per tenant (see ROADMAP.md). - Tempo has no tenant-id attribute. Cross-tenant unions with differing terms are rejected on Tempo. Search responses lose
rootServiceName,rootTraceNameandserviceStats, and the trace start and duration are recomputed from the matching spans, for every user who is not unrestricted. - Denied labels hide, they do not redact.
labels.denyblocks filtering, grouping and listing of a label; the label can still appear in query results (except trace by ID). Use the opt-inlabels.redactto rewrite its values as well; redacted responses are buffered rather than streamed. - Approximate results. Summed Loki statistics, volume, patterns and detected-label cardinalities can count a record once per matching term when terms are not provably disjoint; such responses are marked with
X-Querdian-Approximate: true. - Authentication. Bearer tokens are valid until they expire; there is no revocation list, and an open Loki tail outlives the token and policy changes. Policy revocation is eventually consistent and survives a Kubernetes API outage (the previous snapshot is used).
- Operations. No rate limiting or quotas, no TLS termination (use an ingress or mesh), the management port is unauthenticated. Memory scales with concurrent merged responses (
limits.maxMergeByteseach). - Tested versions. The differential and equivalence suites ran against Prometheus v3.5.0, Loki 3.5.0 and Tempo 2.9.0. Other versions may diverge in corners the parsers do not model. Mimir and Thanos federation behaviour is based on research notes, not on end-to-end tests.
- Scope. Read paths only: no write, ingest, ruler or alert paths. Kubernetes only, HTTP API only (no gRPC or streaming search). The policy dashboard is read-only and meant for administrators (dashboard.md).
- Supply chain. The signing, SBOM and attestation pipeline was exercised end to end by two release candidates (
v0.1.0-rc.1,v0.1.0-rc.2) and verified as described in releasing.md. No third-party security audit has been performed. - Project. Single maintainer; not a CNCF project.
Security
No released version was ever affected by a known vulnerability: this is the first release, and the security-relevant fixes in the CHANGELOG were found before it. Report vulnerabilities as described in SECURITY.md.
Upgrading
First release; nothing to upgrade from.
Commits since v0.1.0-rc.2
Bug fixes
- abd6a3d: fix(quickstart): match the renamed customer_id panel in verify.sh (@geilername)
v0.1.0-rc.2
Changelog
Features
- 85d2c70: feat(chart): publish Artifact Hub metadata and changelog (@geilername)
Verify
cosign verify ghcr.io/querdian-dev/querdian:0.1.0-rc.2 \
--certificate-identity-regexp '^https://github.com/querdian-dev/querdian/\.github/workflows/release\.yml@refs/tags/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comv0.1.0-rc.1
Changelog
Features
- 872f1f1: feat(api): CRD types and manifests for policy.querdian.dev/v1alpha1 (geilername maximilian.marschall@3xm.at)
- 4ec5fcc: feat(auth): OIDC bearer-token authentication (T1.7) (geilername maximilian.marschall@3xm.at)
- b046148: feat(chart): add Helm chart with RBAC, network policies and tracing env (geilername maximilian.marschall@3xm.at)
- 213ce3b: feat(chart): optional Grafana dashboard ConfigMap; keep Go tests out of the packaged chart (geilername maximilian.marschall@3xm.at)
- 354d12b: feat(cmd): register the LogQL and TraceQL enforcers (geilername maximilian.marschall@3xm.at)
- a66d7d3: feat(cmd): register the PromQL enforcer (geilername maximilian.marschall@3xm.at)
- 2487741: feat(config): static config file and flags (geilername maximilian.marschall@3xm.at)
- e33041c: feat(controller): snapshot lifecycle and leader-elected status writer (geilername maximilian.marschall@3xm.at)
- f715343: feat(dashboards): add Grafana overview dashboard and docs (geilername maximilian.marschall@3xm.at)
- 3c07a5b: feat(fanout): merge Loki log stream responses (geilername maximilian.marschall@3xm.at)
- 45de2f7: feat(fanout): redact Tempo search metadata and filter tag lists (geilername maximilian.marschall@3xm.at)
- af75bce: feat(fanout): sum Loki stats, volume and pattern responses (geilername maximilian.marschall@3xm.at)
- 076d5ca: feat(logql): add clean-room lexer (geilername maximilian.marschall@3xm.at)
- 14ad169: feat(logql): add parser, AST and canonical String (T3.2) (geilername maximilian.marschall@3xm.at)
- f8db76f: feat(logql): filter denied labels out of Loki series responses; share the series filter in enforce (geilername maximilian.marschall@3xm.at)
- 995a697: feat(logql): inject policy matchers, scan denied labels, or-lift and fan-out (geilername maximilian.marschall@3xm.at)
- c1b4bb6: feat(logql): label, detected_* and series endpoints with string-set merge (geilername maximilian.marschall@3xm.at)
- 8a260c7: feat(observability): add tracing, metrics, logging and privacy helpers (geilername maximilian.marschall@3xm.at)
- da541d4: feat(policy): DNF normalisation with semantic-equivalence fuzzing (geilername maximilian.marschall@3xm.at)
- 8b8ac73: feat(policy): compile CRDs into indexed rules with status results (geilername maximilian.marschall@3xm.at)
- de7b5f3: feat(policy): snapshot and resolver with tenant binding (geilername maximilian.marschall@3xm.at)
- 1862e80: feat(promql): expand match[] on series, labels and label values (geilername maximilian.marschall@3xm.at)
- 714fa52: feat(promql): filter denied labels out of series responses (geilername maximilian.marschall@3xm.at)
- 29e9ba4: feat(promql): or-lift queries for policies with several terms (geilername maximilian.marschall@3xm.at)
- fe1a323: feat(promql): single-term injection and denied-label checks (geilername maximilian.marschall@3xm.at)
- ea02190: feat(proxy): Loki tail WebSocket fan-out (T3.7) (geilername maximilian.marschall@3xm.at)
- 9b8b131: feat(proxy): apply Plan.ResponseFilter to streamed 2xx responses, failing closed (geilername maximilian.marschall@3xm.at)
- 6b14322: feat(proxy): log the cause of failed merges and fan-outs; surface it in E2E test output (geilername maximilian.marschall@3xm.at)
- e9e3d28: feat(quickstart): 24 h of deterministic demo history and provisioned dashboards (geilername maximilian.marschall@3xm.at)
- 83c228f: feat(quickstart): enable the policy dashboard on localhost:8081 (geilername maximilian.marschall@3xm.at)
- edaf515: feat(quickstart): kind quickstart with Keycloak, Grafana, LGTM stack and verify script (T5.3) (geilername maximilian.marschall@3xm.at)
- 7b57c78: feat(quickstart): redacted-label demo for the order team (geilername maximilian.marschall@3xm.at)
- f21434b: feat(routes): settle Loki detected_labels and POST query via black-box test (geilername maximilian.marschall@3xm.at)
- 44f388e: feat(server): register Loki/Tempo mergers, trace-by-ID filter and per-request policy context (geilername maximilian.marschall@3xm.at)
- cd0db92: feat(server): route WebSocket tail requests through a tail planner and ServeTail (geilername maximilian.marschall@3xm.at)
- f1d3078: feat(server): wire auth, policy, enforcement and proxy into the proxy and management servers (geilername maximilian.marschall@3xm.at)
- b826f7e: feat(site): client-side documentation search (geilername maximilian.marschall@3xm.at)
- 2732148: feat(site): typo-tolerant documentation search (geilername maximilian.marschall@3xm.at)
- 8592968: feat(traceql): add TraceQL parser with filter location and canonical String (geilername maximilian.marschall@3xm.at)
- 86e5415: feat(traceql): add clean-room TraceQL lexer (geilername maximilian.marschall@3xm.at)
- 8ae8986: feat(traceql): filter trace-by-ID responses by policy (geilername maximilian.marschall@3xm.at)
- fa63d79: feat(traceql): inject policy DNF and enforce denied attributes for Tempo (geilername maximilian.marschall@3xm.at)
- 74bfe5a: feat(ui): show label redaction in the policy dashboard (geilername maximilian.marschall@3xm.at)
- f8395db: feat: opt-in redaction of label values in query results (labels.redact) (geilername maximilian.marschall@3xm.at)
- 5b07e0b: feat: optional read-only policy dashboard (geilername maximilian.marschall@3xm.at)
- 4fe659e: feat: repository scaffold and governance files (geilername maximilian.marschall@3xm.at)
Bug fixes
- abb1c02: fix(chart): give every backendProtection target its own policy name (geilername maximilian.marschall@3xm.at)
- dfed768: fix(deps): bump x/net and Tempo protobuf types; filter unmappable Tempo advisories (geilername maximilian.marschall@3xm.at)
- 57d0b71: fix(deps): require Go 1.27.2 (geilername maximilian.marschall@3xm.at)
- 1bfe673: fix(fanout): merge index/volume_range branches that Loki answers with an empty vector (geilername maximilian.marschall@3xm.at)
- 73e6d61: fix(fanout): merge single-step volume_range vectors as matrices (geilername maximilian.marschall@3xm.at)
- fd33e89: fix(logql): express tenant binding of metric queries as a negative matcher (geilername maximilian.marschall@3xm.at)
- be3a0d1: fix(promql): deny U__-escaped label names on label values (geilername maximilian.marschall@3xm.at)
- 0193fa2: fix(proxy): forward client headers by allow-list, reject protocol switches (geilername maximilian.marschall@3xm.at)
- 2bc6eea: fix(proxy): reject every repeated single-valued parameter (geilername maximilian.marschall@3xm.at)
- d063fc7: fix(proxy): withhold upstream error text from redacted requests (geilername maximilian.marschall@3xm.at)
- 21afe09: fix(quickstart): traces dashboard defaults to 12 h so slow spans exist for every user (geilername maximilian.marschall@3xm.at)
- c2f4968: fix(site): use hugo.Data instead of deprecated site.Data (geilername maximilian.marschall@3xm.at)
- a6d8cff: fix(traceql): reject backtick strings in the lexer (geilername maximilian.marschall@3xm.at)
- 5de26cc: fix(traceql): reject quoted attribute names on tag values (geilername maximilian.marschall@3xm.at)
- cd4cbb6: fix: bump Go to 1.27.3 (GO-2026-6809) (geilername maximilian.marschall@3xm.at)
- 70fed4c: fix: resolve go.mod/go.sum conflict markers from t1.6 merge (geilername maximilian.marschall@3xm.at)
Other changes
- ed52cd2: Merge main into t3.8-loki-e2e (geilername maximilian.marschall@3xm.at)
- 6a336af: Merge remote-tracking branch 'origin/main' into feat/label-redaction (geilername maximilian.marschall@3xm.at)
- 23f483c: build(tempopb): copy the OTLP protobuf types instead of importing Tempo (geilername maximilian.marschall@3xm.at)
- 66d67d2: ci,docs: chart and e2e CI jobs, issue/PR templates, CODEOWNERS, contribution requirements...