ADR-0303: Helm / Argo release inventory sample and redaction¶
Argo CD
Applicationis the primary Helm sample; never export secrets — redact sensitive keys.
Theme: 03 · Collection & extraction · Status: Current
from Flux HelmRelease to Argo Application.
Context¶
Product requirements call for tested sample CRs including Helm release metadata
(REQUIREMENTS.md). The first walkthrough uses apps/v1 Deployment
(deployment-inventory example).
Helm stores release state in opaque helm.sh/v1 Secrets (data.release is base64+gzip JSON).
Those objects—and GitOps HelmRelease spec.values—can contain passwords, tokens, and
secretKeyRef blocks. Exporting them to Git, Postgres, or the inventory HTTP API without policy
would leak credentials into logs and public demo repos.
User requirement : inventory must expose chart version and appVersion by default;
deployed values are useful for some teams but must be optional and gated.
Decision¶
Primary GVK (default demo and first sample) — amended ADR-0201¶
argoproj.io/v1alpha1 / Application (Argo CD)
- Structured
statussync history andspec.sourcechart metadata — common in Argo CD estates. - Works with existing JSONPath/CEL extraction.
KollectTargetscopes Applications via namespace/label selectors.
Amended : Argo primary per ADR-0201. Flux
HelmRelease sample may remain secondary.
Version fields (Argo — lock in contract test):
| Attribute | Path (candidate) | Notes |
|---|---|---|
chartVersion |
$.status.sync.revision or history entry chart revision |
Contract test required |
appVersion |
From helm parameters / status.history |
Validate against golden fixture |
revision |
$.status.history[*].revision |
Ordering validated in CI |
valuesChecksum |
$.status.sync.comparedTo digest or equivalent |
Drift without full values export |
Contract test (CI): golden Application fixture in test/schema/ or envtest asserts
Secondary GVK (Flux)¶
helm.toolkit.fluxcd.io/v2 / HelmRelease — optional sample; paths below for Flux shops:
| Attribute | Path | Notes |
|---|---|---|
chartVersion |
$.status.lastAttemptedRevision |
Authoritative for Flux |
appVersion |
$.status.history[0].appVersion |
Contract test for ordering |
valuesChecksum |
$.status.lastAttemptedConfigDigest |
Contract test (CI): golden HelmRelease fixture (secondary) asserts
history[0] is newest (compare lastDeployed / version ordering) and that
lastAttemptedRevision matches expected chart version string.
Secondary GVK (plain Helm; deferred implementation)¶
helm.sh/v1 / Secret (type: helm.sh/release.v1, label owner=helm)
- Covers
helm installwithout Flux. - Not in the public demo sample until Kollect adds a
helm:decode path (or CEL helper) that expandsdata.releaseand exposeschart.metadata.version,chart.metadata.appVersion, andconfig(values). - Never export raw
data.releaseor renderedmanifestblobs.
Two-tier profile model¶
| Profile | Audience | Attributes | Values |
|---|---|---|---|
helm-release-summary (default) |
All teams, public demo | releaseName, chartVersion, appVersion, revision, status, lastDeployed, chart ref, valuesChecksum |
No |
helm-release-values-redacted (opt-in) |
Platform / audit (gated) | Above + scrubbed spec.values |
Yes, redacted |
Public config/samples/ and demo Git repos ship summary only.
Redaction policy (values profile and future operator scrub)¶
Never extract raw Secret.data values by default. Summary attributes come from Helm release
labels/metadata or Flux HelmRelease status—not from decoded values.yaml or opaque storage blobs.
Profile attributes use optional: true and explicit type: string (or int for revision)
so missing history or digest fields do not fail collection on partially reconciled releases.
Always deny export of:
- Raw Helm storage:
data.release, full renderedmanifest - Credential carriers:
valueFrom.secretKeyRef,envFrom.secretRef, TLS key material - Subtrees whose keys match (case-insensitive):
password,passwd,secret,token,apikey,api_key,privatekey,private_key,credential,auth,clientSecret,connectionString
When values extraction is added later, apply the same key filter to any nested values map (JSONPath/CEL scrub before export—not at admission time).
Replace denied values with: {"redacted": true, "reason": "sensitive-key"} (no hashing or
truncation of secrets).
Prefer checksum over full values when drift detection suffices: Flux
status.lastAttemptedConfigDigest / status.history[].configDigest.
Governance: helm-release-values-redacted is restricted via KollectScope and
must use private sinks—not the public demo Git repo.
Admission webhook (Secret.data guard)¶
The Profile validating webhook rejects attribute paths that read Secret.data unless the
Profile carries an explicit opt-in annotation:
metadata:
annotations:
kollect.dev/allow-secret-extraction: "true"
This blocks accidental inventory of base64 credential blobs (including Helm storage
data.release) on the default demo path. The public helm-release-summary sample targets
Flux HelmRelease and never references Secret.data. Plain-Helm Secret profiles remain
deferred until a gated helm: decode path exists.
Implementation: internal/validation/profile.go (ValidateProfile); wired from
internal/webhook/v1alpha1/kollectprofile_webhook.go.
Sample manifests¶
| File | Purpose |
|---|---|
config/samples/kollect_v1alpha1_kollectprofile_argo-application-summary.yaml |
Summary-tier Profile (Argo Application GVK — primary) |
config/samples/kollect_v1alpha1_kollecttarget_argo-applications.yaml |
Example Target scoping team Argo Applications |
config/samples/kollect_v1alpha1_kollectprofile_helm-release-summary.yaml |
Summary-tier Profile (Flux HelmRelease — secondary) |
config/samples/kollect_v1alpha1_kollectprofile_helm-release-values-redacted.yaml |
Values-tier Profile (Flux HelmRelease — gated, scrubbed spec.values) |
config/samples/kollect_v1alpha1_kollecttarget_helm-releases.yaml |
Example Target scoping Flux HelmReleases |
Walkthrough: docs/examples/helm-release-inventory.md (Argo
Application primary; Flux HelmRelease secondary section).
Implementation phases¶
- Now: Argo
Applicationcontract test first (internal/collect/argo_application_contract_test.go), then summary profile + target samples; FluxHelmReleasesample remains secondary; Profile webhook blocksSecret.datawithout opt-in; webhook requirescel:prefix on CEL expressions (ADR-0302); JSONPath filter validation warn-only in Phase 1. - Phase 2+:
helm:release.<field>attribute prefix — decoder expandsdata.releasegzip JSON (chartVersion,appVersion,configvalues); never export raw blob. - Phase 2: export-time scrub via global operator
scrubKeys[]denylist; per-attributeredact: trueoptional later; JSONPath filter validation rejects unsupported filters.
Consequences¶
Positive¶
- Default path is safe for public docs and CI without secret-adjacent blobs.
version/appVersion/ checksum satisfy the primary inventory use case.- Values remain available for enterprise audit when scope, sink, and redaction gates apply.
- Plain-Helm clusters have a documented secondary GVK once decode lands.
Negative¶
- Flux-only primary sample does not cover raw
helm installuntil Secret decode ships. - Redaction is policy-first; operator scrub is follow-up work—authors must not add
spec.valuesto public samples until scrub exists. appVersionalone may not reflect running app version in generic-chart GitOps; optionalimage.tagextraction adds profile complexity.
Open questions¶
- RESOLVED :
helm:release.<field>prefix on attribute paths; decoder expandsdata.release— Phase 2+ (ADR-0201). - RESOLVED : Global
scrubKeys[]operator config first; per-attributeredact: truelater. - RESOLVED : JSONPath filters — webhook warn Phase 1, reject Phase 2.
- RESOLVED :
chartVersionfromstatus.lastAttemptedRevision;history[0]ordering validated by contract test in CI.