Commit Graph
1235 Commits
Author SHA1 Message Date
Thibault VINCENT ed7c2eeb67 fix(ci): push dagger module sync with the App token and let Renovate ignore it 2026-08-04 15:58:46 +02:00
Thibault VINCENT a8d7a622d0 fix(ci): check out the fork repo when syncing the dagger module 2026-08-04 15:33:49 +02:00
Thibault VINCENT d349ce0aef build(dagger): derive the CLI version from dagger.json everywhere 2026-08-04 15:20:15 +02:00
enix-renovate[bot]andThibault VINCENT 0332c8dc75 chore: update github-actions 2026-07-06 16:30:39 +02:00
enix-renovate[bot]andThibault VINCENT 3ac8415353 fix: update go modules (minor) 2026-06-30 18:45:01 +02:00
enix-renovate[bot]andThibault VINCENT b6f58e69a0 fix: update dagger sdk + engine 2026-06-30 18:44:37 +02:00
enix-renovate[bot]andThibault VINCENT 0347819cbb fix: update go modules (patch) 2026-06-30 18:44:08 +02:00
enix-renovate[bot]andThibault VINCENT 766365e907 chore: update renovate docker tag to v43.249.2 2026-06-30 18:43:18 +02:00
enix-renovate[bot]andThibault VINCENT 21af0d15c0 chore: update renovate/renovate docker tag to v43.249.2 2026-06-30 18:43:03 +02:00
enix-renovate[bot]andThibault VINCENT bd9778e4ce chore: update trivy docker tag to v0.72.0 2026-06-30 18:42:12 +02:00
enix-renovate[bot]andThibault VINCENT c9525d9e41 chore: update actions/checkout action to v7 2026-06-30 18:41:58 +02:00
enix-renovate[bot]andThibault VINCENT 69433985ef chore: update kube-prometheus-stack docker tag to v87 2026-06-30 18:41:37 +02:00
enix-renovate[bot]andThibault VINCENT 9453000f47 chore: update alpine docker tag to v3.24.1 2026-06-30 18:41:17 +02:00
enix-renovate[bot]andThibault VINCENT 0844fcdd0d chore: update dependency helm/helm to v4.2.2 2026-06-30 18:15:41 +02:00
enix-renovate[bot]andThibault VINCENT c650c98cfe chore: update helm docker tag to v4.2.2 2026-06-30 18:15:20 +02:00
enix-renovate[bot]andThibault VINCENT c35c7c9005 chore: update trivy docker tag to v0.71.2 2026-06-30 18:14:59 +02:00
enix-renovate[bot]andThibault VINCENT 92dfb9291d chore: update rancher/k3s docker tag to v1.36.2-k3s1 2026-06-30 18:08:50 +02:00
enix-renovate[bot]andThibault VINCENT f04a94a832 chore: update github-actions 2026-06-30 18:08:25 +02:00
enix-renovate[bot]andThibault VINCENT 64a0b33733 chore: update golang toolchain 2026-06-30 18:01:02 +02:00
Thibault VINCENTandThibault VINCENT a1a454659e ci(security): drop persisted credentials on checkouts, shallow gitleaks fetch 2026-06-16 17:49:18 +02:00
Thibault VINCENTandThibault VINCENT 1266ec8b74 ci(security): scan secrets via gitleaks CLI in Dagger, not gitleaks-action 2026-06-16 17:49:18 +02:00
Thibault VINCENTandThibault VINCENT f103b6d3f8 test(registry,scenarios): JKS gating-off, pre-init idempotency, self-test tryEmpty parity 2026-06-15 18:12:17 +02:00
Thibault VINCENTandThibault VINCENT a25177d096 test(chart): render ratchets for alert overrides, rulePrefix, disableBuiltinAlertGroup guard 2026-06-15 18:12:17 +02:00
Thibault VINCENTandThibault VINCENT 5a534f79fb test(registry): assert CollisionNever survivor identity and CRL metric gating 2026-06-15 18:12:17 +02:00
Thibault VINCENTandThibault VINCENT 7ae955e6a0 feat(cabundle): record reflector watch errors via x509_kube_transport_errors_total 2026-06-15 18:12:17 +02:00
Thibault VINCENTandThibault VINCENT a068fc5308 test(k8s): assert bad_passphrase bundle emission, two-source accumulation, JksTryEmpty switch 2026-06-15 18:12:17 +02:00
Thibault VINCENTandThibault VINCENT 2da9c89acb fix(cert/jks): bound JCEKS cert/key lengths against remaining payload 2026-06-15 18:12:17 +02:00
Thibault VINCENTandThibault VINCENT e86fcde4ef feat(registry): pre-initialise counter series at zero per source
Five user-facing counters had a UX hit: prometheus.NewCounterVec only
materialises a series the first time WithLabelValues(...).Inc() is
called, so an exporter running cleanly produced no series at all for
x509_source_errors_total, x509_kube_transport_errors_total,
x509_cert_collision_dropped_total and the two
x509_{pkcs12,jks}_passphrase_failures_total. Dashboards couldn't tell
'healthy' from 'metric not reporting', rate()/increase() needed two
real events to compute anything, and the chart's
SourceErrors[Sustained] / KubeTransportErrors[Sustained] alerts had
ambiguous PromQL semantics on first event.

PreInitBundleSource(kind, name) and PreInitKubeTransport(name,
resources, namespaceInformer) on Registry materialise the expected
series at zero by calling WithLabelValues without .Inc(). The static
reason sets they iterate (cert.BundleReasons, cert.KubeTransportPerResourceReasons,
cert.ReasonNamespaceSyncFail) live in pkg/cert/reason.go for a single
source of truth — dynamic 'http_NNN' reasons are deliberately
excluded since they can't be enumerated.

cmd/x509-certificate-exporter/main.go calls the right combination
for each config.Source kind right after buildSource: kubernetes
sources get both bundle init (kube-secret, kube-configmap) and
transport init (per-resource + namespace informer when label rules
require it); file/kubeconfig/cabundle sources just get the bundle
init for their respective kind. Cardinality cost is bounded by
declared reasons × source count — about 50 series per source — and
negligible against the per-cert series the exporter produces in
normal operation.

TestPreInitMaterializesZeroSeries in pkg/registry locks in the exact
series count materialised by a representative call and asserts every
emitted counter value is 0.
2026-06-15 18:02:40 +02:00
Thibault VINCENTandThibault VINCENT b0fb9cee31 bump(go): upgrade dependencies 2026-06-15 17:30:35 +02:00
Thibault VINCENTandThibault VINCENT 564e949cfb ci: bump Dagger to 0.21.6 2026-06-15 17:30:35 +02:00
Thibault VINCENTandThibault VINCENT b0df976080 feat(container): bump Alpine base image to 3.24.0 2026-06-15 17:30:35 +02:00
Thibault VINCENTandThibault VINCENT a6d321f188 ci: bump renovate version 2026-06-15 17:30:35 +02:00
Thibault VINCENTandThibault VINCENT b79ed298a1 chore(Tiltfile): bump kube-prometheus-stack version 2026-06-15 17:30:35 +02:00
Thibault VINCENTandThibault VINCENT 8b058ba558 ci: bump action versions 2026-06-15 17:30:35 +02:00
enix-renovate[bot]andThibault VINCENT 56d52a5088 fix: update go modules (patch) 2026-06-15 17:10:20 +02:00
enix-renovate[bot]andThibault VINCENT 2f0311a354 chore: update trivy docker tag to v0.71.1 2026-06-15 17:02:14 +02:00
enix-renovate[bot]andThibault VINCENT 69e244467d chore: update github-actions 2026-06-15 16:57:44 +02:00
enix-renovate[bot]andThibault VINCENT e66c84ad1d chore: update dependency helm/helm to v4.2.1 2026-06-15 16:57:15 +02:00
enix-renovate[bot]andThibault VINCENT 5c16c3e611 security: update go toolchain to v1.26.4 [security] 2026-06-15 16:56:45 +02:00
enix-renovate[bot]andThibault VINCENT 31cc3efe64 chore: update renovate/renovate docker tag to v43.218.0 2026-06-15 16:56:27 +02:00
enix-renovate[bot]andThibault VINCENT d21b62868d chore: update kube-prometheus-stack docker tag to v85.4.0 2026-06-15 16:56:07 +02:00
enix-renovate[bot]andThibault VINCENT f3e4b78e47 chore: update trivy docker tag to v0.71.0 2026-06-15 16:55:44 +02:00
enix-renovate[bot]andThibault VINCENT e08da567af chore: update golang toolchain to v1.26.4 2026-06-15 16:55:20 +02:00
enix-renovate[bot]andThibault VINCENT 0dc0d9d976 chore: update helm docker tag to v4.2.1 2026-06-15 16:54:59 +02:00
Thibault VINCENTandThibault VINCENT d73f3c0c76 feat(chart): per-alert expr and for overrides + render ratchet
Two new value maps under `prometheusRules`:

- `alertExprOverrides` — alertName -> custom Prometheus expression
  that replaces the default `expr:`. Closes #253: multi-cluster setups
  can inject `max by (cluster, …)` aggregations or label filters
  without having to disableBuiltinAlertGroup and reconstruct everything.
- `alertForOverrides` — alertName -> `for:` duration that replaces
  the default. Schema-validated against Prometheus's duration syntax.

Both maps key on bare alert names (no `rulePrefix`). Overrides are
taken whole — no merge with the default. Single-line and multi-line
strings both render correctly: the template now emits each `expr:`
via `| quote` which wraps in a YAML double-quoted scalar (newlines
serialise to \n; Prometheus parses the resulting string identically
to the multi-line form).

Test ratchets added alongside:

- test/schema/valid/prometheusrules-alert-overrides.yaml — valid case
  covering both maps, with a multi-line expr to exercise the quoting
  path.
- test/schema/invalid/prometheusrules-alertforoverrides-bad-duration
  — paired with .expect.txt, ensures the duration regex rejects
  natural-language inputs ("30 minutes").
- test/render/prometheusrules-default-alerts.{yaml,expect-pass.txt} —
  new ratchet under the existing TestHelmRender machinery (extended
  to support "positive substring" checks via .expect-pass.txt). Locks
  in the exact 13 alert names the chart ships by default: any rename
  or accidental removal breaks the test at PR time.

Docs:

- chart/README.md.gotmpl alert table refreshed — was still on the v3
  layout (X509ExporterReadErrors + 3 cert alerts). Now lists all 13.
- values.yaml gets the two new map keys with descriptions and
  commented examples.
v4.2.0-rc.1
2026-05-25 19:18:58 +02:00
Thibault VINCENTandThibault VINCENT 82e96e11a3 refactor(chart)!: rebuild PrometheusRule template, rename v3-era alerts
MAJOR CHANGE: PrometheusRule alert names and chart value keys
renamed. Users with AlertManager routing or silencing rules that
reference the old names must update them on upgrade. Migration map:

  X509ExporterReadErrors     -> SourceErrors (+ SourceErrorsSustained)
  alertOnReadErrors          -> alertOnSourceErrors
  readErrorsSeverity         -> sourceErrorsSeverity (+ ...SustainedSeverity)
  alertOnPassphraseFailures  -> alertOnKeystorePassphraseFailures
  passphraseFailuresSeverity -> keystorePassphraseFailuresSeverity
  alertOnCertificateErrors   -> alertOnCertificateError       (singular)
  certificateErrorsSeverity  -> certificateErrorSeverity      (singular)
  alertOnCertificateCollisions -> alertOnCertificateCollision (singular)
  certificateCollisionsSeverity -> certificateCollisionSeverity
  alertOnCRLs                -> (removed; CRL alerts always-on like Renewal/Expiration)

Substantive changes alongside the renames:

- SourceErrors gets the same two-band shape as KubeTransportErrors
  (warning at >5/15min for 5m, critical for 30m). The previous "fire
  on any single error" expression paged on a single malformed PEM,
  which was indistinguishable from a real outage.
- SourceErrors and KubeTransportErrors both aggregate over
  reason/resource via 'sum without (...)', so the alert routes by
  source. The breakdown stays available on the metric for triage.
- CRLNeedsRefresh and CRLStale no longer require alertOnCRLs — same
  policy as CertificateRenewal/Expiration. The underlying x509_crl_*
  series only exist when a CRL is actually observed, so installs that
  don't watch CRLs get nothing.
- CertificateRenewal expression now anchors to (not_after - now) > 0,
  so it doesn't keep firing alongside CertificateExpiration once a
  cert is past its NotAfter (those two alerts had identical truth
  values in the late-life window).
- CertificateExpiration description distinguishes 'expires in X' from
  'expired X ago' via humanizeDuration's sign, so the message stays
  accurate after the cert is past NotAfter.
- CertificateError, CertificateNotYetValid, CertificateRenewal and
  CertificateExpiration each get inline comments explaining their
  dependency or always-on status — consistent with the recent
  KubeTransportErrors / CertificateCollision additions.

Template hygiene:

- Three helper templates in _helpers.tpl: alertExtraLabels and
  alertExtraAnnotations (replace 12 duplicated if-blocks);
  alertLocationSuffix (replaces the 5 sites that re-emit the
  "{{if $labels.secret_name}}in Kubernetes secret …{{else}}at
  location …{{end}}" literal — when we flipped quotes to backticks
  recently, 5 edits were required; the helper would have made that
  one).
- All alerts stay in a single PrometheusRule group. Groups in
  PrometheusRule only matter for sequential recording-rule ordering,
  per-group evaluation intervals, or execution isolation — none of
  which apply here. AlertManager routes on labels (alertname,
  severity), not on group names, so splitting by domain would
  fragment without buying anything; worse, it would trap users who
  add recording rules via extraAlertGroups into the wrong group.
  Category-based routing on the consumer side is straightforward via
  alertname regex (Source.*, Cert.*, CRL.*, Kube.*).
2026-05-25 19:18:58 +02:00
Thibault VINCENTandThibault VINCENT 52e9c2e66c feat: emit x509_kube_transport_errors_total for LIST/WATCH/informer failures
The Kubernetes source's transport layer had 9 distinct failure sites
that surfaced only as logs: LIST failure (with retry/backoff), WATCH
start failure, WATCH stream Error event, watch flap (close within 5s
of open), and namespace informer sync timeout — once for secrets, once
for configmaps. None had a corresponding metric, so an exporter that
was functionally up but operationally degraded (token rotation drift,
apiserver throttling, intermittent network) was invisible to ops.

Introduce x509_kube_transport_errors_total{source_name,resource,reason}
incremented at each of the 9 sites via a new Recorder interface on
k8s.Options. The interface keeps the source decoupled from
*registry.Registry; nil is valid (the unmetered path keeps the old
log-only behaviour) and tests plug a fake. main.go wires the actual
registry through.

Ship a paired KubeTransportErrors PrometheusRule alert (opt-in via
alertOnKubeTransportErrors, default true). docs/metrics.md documents
the new metric, the five reason codes, and cross-links the alert.
2026-05-25 19:18:58 +02:00
Thibault VINCENTandThibault VINCENT 5291d5a989 feat: split collision counter metric into total + dropped
Detect-only x509_cert_collision_total ticks on every scrape under the
default CollisionAuto policy, even when the discriminator label
silently resolves the overlap and no certificate is dropped. Alerting
on it produced perma-firing pages for a benign config quirk.

Introduce x509_cert_collision_dropped_total: counts only the items
the registry actually threw away (CollisionNever policy). The
CertificateCollision PrometheusRule now points at the new counter so
the alert fires only when data is genuinely lost. The detect-only
counter stays available for dashboards and diagnostics.

Tests in pkg/registry cement the contract: dropped stays at 0 under
CollisionAuto and is positive under CollisionNever. docs/metrics.md
documents both counters and cross-links them.
2026-05-25 19:18:58 +02:00
Thibault VINCENTandThibault VINCENT ee273b4b04 feat(chart): add SourceDown, PassphraseFailures, NotYetValid, Collision alerts
Four new opt-in alerts close observability holes the existing rules
left uncovered:

- SourceDown (critical) — x509_source_up == 0 for 5m. Catches RBAC
  failures, persistent K8s API errors, unreadable file paths. A source
  that never converges would otherwise be silent while certs it should
  watch are not checked.
- KeystorePassphraseFailures (warning) — increase on
  x509_{pkcs12,jks}_passphrase_failures_total. A misconfigured
  passphraseKey / passphraseSecretRef previously only leaked into
  logs and an unwatched counter.
- CertificateNotYetValid (warning) — x509_cert_not_before > time().
  Depends on exposeNotBeforeMetric: true; gated for explicit opt-in.
- CertificateCollision (warning) — increase on x509_cert_collision_total.
  Two certs sharing a label set means one is silently invisible.

Each alert ships with an alertOnXxx toggle (default true) and its own
severity in values.yaml. Schema + README regenerated. helm lint, helm
template, schema fixtures and helm-examples all pass.
2026-05-25 19:18:58 +02:00
Thibault VINCENTandThibault VINCENT a782c9b20d fix(chart): rename x509_read_errors to x509_source_errors_total and use increase()
The X509ExporterReadErrors PrometheusRule alert matched x509_read_errors,
the v3 metric name. The v4 exporter emits x509_source_errors_total
instead — the alert never fired since the v4 rewrite. Same stale name
lingered in the values.yaml extraAlertGroups example and across 8
Grafana dashboard panels. All three sites renamed in lockstep.

Switch the alert's range function from delta() to increase() while
we're here. delta() is documented for gauges; for monotonic counters
increase() is the canonical choice and correctly handles counter
resets across exporter restarts.
2026-05-25 19:18:58 +02:00