deploy: Helm chart for the api and engine roles (#61, #62) - #103
Merged
Conversation
Findings, their append-only trail, and the administrative audit log grow forever. This bounds that — and, mostly, defines what will never be deleted. **Every window is off by default.** This database is evidence: it records where an organisation's secrets are and who did what about each one. Deleting some of it has to be a decision somebody made, not something that starts happening because they upgraded. With nothing configured the purge runs and removes nothing. **Never eligible, whatever the window says:** * open findings — an unresolved secret is not old news; * analyst decisions (`false_positive`, `accepted_risk`, *manual* resolutions) — a judgement that expires silently means the finding returns next scan with nobody remembering it was already considered. Only `auto` resolutions, which are an inference from absence, are eligible; * suppressed findings — deleting one resurrects it as new on the next scan, the exact outcome the suppression exists to prevent (ADR 0008); * an open finding's events, however old. The finding clock runs from the decision, not the first sighting: a secret found two years ago and auto-resolved yesterday is one day old here. Purges audit themselves. Deleting evidence is an administrative action, so a round that removed anything writes `retention.purged` with the counts *and the windows that justified them*, so the row still explains itself when the settings have since changed. A round that deleted nothing writes nothing — an audit log full of "deleted 0 rows" every minute is one nobody reads. That row is subject to the audit window like any other, which is honest rather than a special case hiding from the policy it enforces. Runs last in the maintenance round, under the existing advisory lock, and deletes in batches so the first purge on a database that has never had one cannot hold locks for minutes. `python -m iceberg_api retention-purge` runs one now. `docs/retention.md` covers the windows, the never-delete rules, and guidance on choosing them (including that "you cannot get these rows back"). Closes #73 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`deploy/helm/icebergsst/`: api Deployment/Service/Ingress, engine Deployment with an HPA, a pre-upgrade migration Job, a ConfigMap, two Secrets, two ServiceAccounts, and an optional NetworkPolicy pair. **Two Secrets, not one**, which is the whole shape of the chart. The api's holds the database URL, master key, session secret and OIDC client secret; the engine's holds a broker URL and its own token. The engine Deployment does not reference the api's Secret at all, so ADR 0002 is enforced by what is mounted rather than by everyone remembering — and separate ServiceAccounts let a cluster RBAC policy say the same thing. **Migrations are a `pre-install,pre-upgrade` hook** running the api image and `python -m iceberg_api migrate` — the entry point compose and the migration tests already use, so what runs in production is what has been exercised. `hook-delete-policy` keeps a failed Job for its logs. **Postgres and Redis are deliberately not in the chart.** A bundled database is a development convenience that becomes a production liability: no backups, no failover, and a `helm uninstall` that takes the findings with it. Engine autoscaling is on by default and the api's is off — engines hold leases, not state, and a lapsed lease is reclaimed (ADR 0009), while the api's useful replica count is driven by availability. The HPA's scale-down window is 5 minutes so a quiet moment after a scheduled sweep does not kill engines holding leases that then have to lapse and be re-done. Every workload runs the restricted Pod Security Standard: non-root uid 10001, no privilege escalation, read-only root filesystem, all capabilities dropped, RuntimeDefault seccomp, resource requests set. `deploy/helm/verify-chart.sh` (`make helm-verify`, and a CI job) lints, renders with `example-values.yaml`, renders again against externally-managed Secrets, and then asserts twelve properties of the *rendered manifests* that `helm lint` cannot see. The cheap textual half runs in the ordinary suite. check-yaml skips the chart templates: they are Go templates that only become YAML once rendered, which is what helm-verify checks. Verified with helm 4.2.3: lint clean, both renders succeed, all twelve checks pass. Closes #61 Closes #62 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
deploy/helm/icebergsst/— apiDeployment/Service/Ingress, engineDeployment+ HPA, apre-upgrade migration
Job, aConfigMap, twoSecrets, twoServiceAccounts, and anoptional
NetworkPolicypair.Two Secrets, not one
This is the shape of the whole chart. The api's Secret holds the database URL, master key, session
secret and OIDC client secret. The engine's holds a broker URL and its own token — no database
credential, no master key, because there is nothing an engine could correctly do with either.
The engine
Deploymentdoes not reference the api's Secret at all, so ADR 0002 is enforced by whatis mounted rather than by everyone remembering. Separate ServiceAccounts mean a cluster RBAC
policy can express the same boundary.
Production should create both out of band and set
secrets.existingApiSecret/existingEngineSecret— values passed to Helm end up in the release's stored manifest, which isnot where a master key belongs. The inline values exist so a first install works; the docs say
plainly that they are not the production path.
Other decisions worth a look
becomes a production liability: no backups, no failover, and a
helm uninstallthat takes thefindings with it.
pre-install,pre-upgradehook running the api image andpython -m iceberg_api migrate— the same entry point compose and the migration tests use, soproduction runs what has been exercised.
hook-delete-policyleaves a failed Job behind forits logs.
lapsed lease is reclaimed (ADR 0009); the api's useful replica count is availability, not CPU.
The HPA has a 5-minute scale-down window so the quiet moment after a scheduled sweep does not
kill engines mid-lease and cause re-done work.
read-only root filesystem, all capabilities dropped, RuntimeDefault seccomp, resource requests.
NOTES.txtwarns about the two settings that are wrong by default for a real cluster —trustedProxyHopsat 0 behind an ingress, and secrets supplied via values.Verification
deploy/helm/verify-chart.sh(make helm-verify, plus a CI job) lints, renders withexample-values.yaml, renders again against externally-managed Secrets, then asserts twelveproperties of the rendered manifests that
helm lintcannot see — the engine mounting only itsown Secret and carrying no DB config, migrations as a pre-upgrade hook on the api image, nothing
migrating on start, every workload hardened with requests set, the engine having an HPA and no
Service. The cheap textual half runs in the ordinary suite.
Run against helm 4.2.3: lint clean, both renders succeed, all twelve checks pass.
check-yamlnow skips the chart templates: Go templates only become YAML once rendered, which isexactly what
helm-verifychecks.Closes #61
Closes #62
🤖 Generated with Claude Code