From a163963eeb465f2d6476ef489b5c53c10eb8a4d5 Mon Sep 17 00:00:00 2001 From: Thibault VINCENT Date: Wed, 13 May 2026 17:33:26 +0200 Subject: [PATCH] docs(README, metrics): announce JKS/JCEKS support and x509_jks_passphrase_failures_total --- README.md | 4 ++++ chart/README.md | 13 ++++++++----- chart/README.md.gotmpl | 4 ++++ chart/values.schema.json | 38 +++++++++++++++++++------------------- docs/metrics.md | 25 +++++++++++++++++++++---- 5 files changed, 56 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 520b67c..ab40793 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ cluster it observes, but equally happy as a standalone binary. - **Memory-safe Kubernetes watch** — RAM stays flat instead of spiking; on Secret-heavy clusters, memory limits drop ~10×. - **Richer PKCS#12 wiring** — full keystore + truststore coverage, flexible passphrase sourcing. - **DER consumption** — raw cert / CRL blobs as served by HTTP CRL Distribution Points are now first-class inputs. +- **JKS / JCEKS support** — Java KeyStore and JCEKS stores. - **CRL freshness monitoring** — Certificate Revocation Lists are tracked alongside certs, with alerts before they go stale. - **Surface workload metadata** — lift watched resource labels onto emitted certificate series. - **Supply-chain hardened** — SLSA Build L3 provenance, cosign-signed binaries, images and chart, SBOM attestations. @@ -55,6 +56,9 @@ cluster it observes, but equally happy as a standalone binary. - **PKCS#12** keystores and truststores, with passphrase pulled from a sibling key in the same Secret, an external file, a cross-namespace Secret reference, or none (`tryEmptyPassphrase`). +- **JKS / JCEKS** keystores and truststores — magic-byte auto-detection + between JKS and JCEKS; passphrase from a sibling key, external file, + or `tryEmptyPassphrase`. - **Certificate Revocation Lists** — `X509 CRL` PEM blocks (intermixed freely with `CERTIFICATE` blocks) and raw DER `*.crl` files (`format: der`) are parsed into the dedicated `x509_crl_*` family so diff --git a/chart/README.md b/chart/README.md index 064fb8f..c213054 100644 --- a/chart/README.md +++ b/chart/README.md @@ -50,6 +50,7 @@ cluster it observes, but equally happy as a standalone binary. - **Memory-safe Kubernetes watch** — RAM stays flat instead of spiking; on Secret-heavy clusters, memory limits drop ~10×. - **Richer PKCS#12 wiring** — full keystore + truststore coverage, flexible passphrase sourcing. - **DER consumption** — raw cert / CRL blobs as served by HTTP CRL Distribution Points are now first-class inputs. +- **JKS / JCEKS support** — Java KeyStore and JCEKS stores. - **CRL freshness monitoring** — Certificate Revocation Lists are tracked alongside certs, with alerts before they go stale. - **Surface workload metadata** — lift watched resource labels onto emitted certificate series. - **Supply-chain hardened** — SLSA Build L3 provenance, cosign-signed binaries, images and chart, SBOM attestations. @@ -64,6 +65,9 @@ cluster it observes, but equally happy as a standalone binary. - **PKCS#12** keystores and truststores, with passphrase pulled from a sibling key in the same Secret, an external file, a cross-namespace Secret reference, or none (`tryEmptyPassphrase`). +- **JKS / JCEKS** keystores and truststores — magic-byte auto-detection + between JKS and JCEKS; passphrase from a sibling key, external file, + or `tryEmptyPassphrase`. - **Certificate Revocation Lists** — `X509 CRL` PEM blocks (intermixed freely with `CERTIFICATE` blocks) and raw DER `*.crl` files (`format: der`) are parsed into the dedicated `x509_crl_*` family so @@ -165,15 +169,14 @@ The default install runs a single Deployment watching `kubernetes.io/tls` Secrets across all namespaces — disable with `secretsExporter.enabled: false`. -### Multiple Secret types, PKCS#12, and JKS +### Multiple Secret types and PKCS#12 `secretsExporter.secretTypes` accepts any mix of types and keys: a literal `key` (regex `^$` is built for you) or a `keyPatterns` list for full regex control, with optional `format: pkcs12` plus a -`pkcs12:` block, or `format: jks` plus a `jks:` block. Passphrases -are pulled from a sibling Secret key (`pkcs12.passphraseKey` / -`jks.passphraseKey`), an external file, a cross-namespace Secret ref -(pkcs12 only), or skipped entirely with `tryEmptyPassphrase: true` for +`pkcs12:` block. Passphrases are pulled from a sibling Secret key +(`pkcs12.passphraseKey`), an external file, a cross-namespace Secret +ref, or skipped entirely with `tryEmptyPassphrase: true` for passwordless keystores. ### Watching ConfigMaps diff --git a/chart/README.md.gotmpl b/chart/README.md.gotmpl index 628bf9a..f98e5df 100644 --- a/chart/README.md.gotmpl +++ b/chart/README.md.gotmpl @@ -50,6 +50,7 @@ cluster it observes, but equally happy as a standalone binary. - **Memory-safe Kubernetes watch** — RAM stays flat instead of spiking; on Secret-heavy clusters, memory limits drop ~10×. - **Richer PKCS#12 wiring** — full keystore + truststore coverage, flexible passphrase sourcing. - **DER consumption** — raw cert / CRL blobs as served by HTTP CRL Distribution Points are now first-class inputs. +- **JKS / JCEKS support** — Java KeyStore and JCEKS stores. - **CRL freshness monitoring** — Certificate Revocation Lists are tracked alongside certs, with alerts before they go stale. - **Surface workload metadata** — lift watched resource labels onto emitted certificate series. - **Supply-chain hardened** — SLSA Build L3 provenance, cosign-signed binaries, images and chart, SBOM attestations. @@ -64,6 +65,9 @@ cluster it observes, but equally happy as a standalone binary. - **PKCS#12** keystores and truststores, with passphrase pulled from a sibling key in the same Secret, an external file, a cross-namespace Secret reference, or none (`tryEmptyPassphrase`). +- **JKS / JCEKS** keystores and truststores — magic-byte auto-detection + between JKS and JCEKS; passphrase from a sibling key, external file, + or `tryEmptyPassphrase`. - **Certificate Revocation Lists** — `X509 CRL` PEM blocks (intermixed freely with `CERTIFICATE` blocks) and raw DER `*.crl` files (`format: der`) are parsed into the dedicated `x509_crl_*` family so diff --git a/chart/values.schema.json b/chart/values.schema.json index 264b89c..aacf79c 100644 --- a/chart/values.schema.json +++ b/chart/values.schema.json @@ -1188,6 +1188,25 @@ ], "required": [] }, + "jks": { + "additionalProperties": false, + "properties": { + "passphrase": { + "type": "string" + }, + "passphraseFile": { + "type": "string" + }, + "passphraseKey": { + "type": "string" + }, + "tryEmptyPassphrase": { + "type": "boolean" + } + }, + "required": [], + "type": "object" + }, "key": { "type": "string" }, @@ -1235,25 +1254,6 @@ "required": [], "type": "object" }, - "jks": { - "additionalProperties": false, - "properties": { - "passphrase": { - "type": "string" - }, - "passphraseFile": { - "type": "string" - }, - "passphraseKey": { - "type": "string" - }, - "tryEmptyPassphrase": { - "type": "boolean" - } - }, - "required": [], - "type": "object" - }, "type": { "minLength": 1, "type": "string" diff --git a/docs/metrics.md b/docs/metrics.md index 7cb057d..ba5fcc9 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -37,6 +37,7 @@ The exporter splits its output into four families: | Per-source | `x509_source_errors_total` | counter | always | | Per-source | `x509_kube_watch_resyncs_total` | counter | Kubernetes sources only | | Per-source | `x509_pkcs12_passphrase_failures_total` | counter | auto: when any source declares `format: pkcs12` | +| Per-source | `x509_jks_passphrase_failures_total` | counter | auto: when any source declares `format: jks` | | Per-source | `x509_cert_collision_total` | counter | always | | Diagnostic | `x509_parse_duration_seconds` | histogram | `metrics.exposeDiagnostics: true` | | Diagnostic | `x509_kube_request_duration_seconds` | histogram | `metrics.exposeDiagnostics: true`, Kubernetes sources only | @@ -410,6 +411,21 @@ was wrong. A spike usually means a Secret was rotated but the sibling passphrase key wasn't, or a `passphraseFile` was stale. +### `x509_jks_passphrase_failures_total` + +JKS / JCEKS keystore decoding attempts that failed because the passphrase +was wrong. + +- **Type**: counter +- **Labels**: `source_name` +- **Auto-gated**: registered only when at least one source declares + `format: jks` (file source `formats:`, kubernetes source + `secrets.types[].format`, or `configMaps.format`). Deployments + without JKS don't see the metric in `/metrics` at all. + +A spike usually means a Secret was rotated but the sibling passphrase +key wasn't, or a `passphraseFile` was stale. + ### `x509_cert_collision_total` Number of times the registry detected two distinct certificates that @@ -512,9 +528,9 @@ etc.) into the internal certificate representation. - **Buckets**: `0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5` seconds - **Emitted only when `metrics.exposeDiagnostics` is `true`.** -`format` takes one of `pem` or `pkcs12`. PKCS#12 is meaningfully slower -because of the KDF — expect millisecond-range parse times on PEM and -double-digit-millisecond on PKCS#12. +`format` takes one of `pem`, `pkcs12`, `der`, or `jks`. PKCS#12 and JKS are +meaningfully slower because of their KDF — expect millisecond-range parse +times on PEM/DER and double-digit-millisecond on PKCS#12/JKS. ### `x509_kube_request_duration_seconds` @@ -604,7 +620,8 @@ metrics provider with the synthetic `source_kind="kubernetes"`, | `bad_pem` | any source running the PEM parser (file, kubeconfig, kube-secret with `format: pem`, kube-configmap) | PEM block present but malformed | | `no_certificate_found` | same | The bundle decoded successfully but contained no `CERTIFICATE` block | | `bad_pkcs12` | any source running the PKCS#12 parser (file with `formats: [pkcs12]`, kube-secret / kube-configmap with `format: pkcs12`) | PKCS#12 archive malformed or uses an unsupported algorithm | -| `bad_passphrase` | same | PKCS#12 archive structurally valid but the configured passphrase was wrong | +| `bad_jks` | any source running the JKS parser (file with `formats: [jks]`, kube-secret / kube-configmap with `format: jks`) | JKS / JCEKS archive malformed or magic bytes not recognised | +| `bad_passphrase` | PKCS#12 or JKS parser | Archive structurally valid but the configured passphrase was wrong | | `read_failed` | `file`, `kubeconfig` | Generic I/O error opening or reading a file | | `permission_denied` | `file` | EACCES on a watched path | | `not_found` | `file` | Path disappeared between announcement and read |