From c32e79dcb45d80083880f9ef6fa1ffd0f7c96722 Mon Sep 17 00:00:00 2001 From: Nayeem Syed Date: Tue, 29 Sep 2026 07:10:12 +0100 Subject: [PATCH] feat(helm): support externally managed secret via secrets.existingSecret (#323) The chart preserves generated secrets across upgrades with a cluster lookup, which is only populated when Helm has a live cluster connection. GitOps tools render manifests without one, so every sync regenerates SERVER_SECRET and friends and invalidates all sessions. Setting secrets.existingSecret makes the chart skip its own Secret and point every service at the named one, so it can come from External Secrets Operator, Sealed Secrets, Vault, or kubectl. --- helm/huly/README.md | 49 +++++++++++++++++++++++++++++++- helm/huly/templates/_helpers.tpl | 6 +++- helm/huly/templates/secret.yaml | 2 ++ helm/huly/values.yaml | 15 ++++++++++ 4 files changed, 70 insertions(+), 2 deletions(-) diff --git a/helm/huly/README.md b/helm/huly/README.md index ae742cb..da8ea0a 100644 --- a/helm/huly/README.md +++ b/helm/huly/README.md @@ -104,6 +104,50 @@ helm install huly ./helm/huly \ --set external.elastic=https://es.example.com:9200 ``` +## Externally Managed Secrets (GitOps) + +By default the chart generates its own Secret and preserves the values across `helm upgrade` +by looking up the Secret already in the cluster. + +That lookup only works when Helm has a live cluster connection. Tools that render manifests +without one — ArgoCD, Flux, `helm template`, `--dry-run` — always see an empty lookup, so every +render regenerates `SERVER_SECRET`, `COCKROACH_PASSWORD` and the rest, which invalidates all +sessions on each sync. + +Set `secrets.existingSecret` to a Secret you manage yourself. The chart then renders no Secret +of its own and points every service at the named one, so it can be produced by External Secrets +Operator, Sealed Secrets, Vault, or `kubectl create secret`. + +```bash +kubectl create secret generic huly-secrets -n \ + --from-literal=SERVER_SECRET="$(openssl rand -hex 16)" \ + --from-literal=COCKROACH_PASSWORD="$(openssl rand -hex 12)" \ + --from-literal=REDPANDA_SUPERUSER_PASSWORD="$(openssl rand -hex 12)" \ + --from-literal=AIBOT_PASSWORD="$(openssl rand -hex 12)" \ + --from-literal=CR_DB_URL='postgres://selfhost:@cockroach:26257/defaultdb' \ + --from-literal=STORAGE_CONFIG='minio|minio?accessKey=&secretKey=' + +helm install huly ./helm/huly \ + --set domain=huly.mysite.com \ + --set secrets.existingSecret=huly-secrets +``` + +| Key | Required when | +|-----|---------------| +| `SERVER_SECRET` | always | +| `CR_DB_URL` | always | +| `STORAGE_CONFIG` | always | +| `COCKROACH_PASSWORD` | `cockroach.enabled=true` | +| `REDPANDA_SUPERUSER_PASSWORD` | `redpanda.enabled=true` | +| `AIBOT_PASSWORD`, `OPENAI_API_KEY` | `aibot.enabled=true` | +| `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` | `auth.google.clientId` is set | +| `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET` | `auth.github.clientId` is set | +| `OPENID_CLIENT_ID`, `OPENID_CLIENT_SECRET`, `OPENID_ISSUER` | `auth.oidc.clientId` is set | +| `GITHUB_APP_ID`, `GITHUB_APP_CLIENT_ID`, `GITHUB_APP_CLIENT_SECRET`, `GITHUB_APP_PRIVATE_KEY`, `GITHUB_APP_WEBHOOK_SECRET` | `githubIntegration.enabled=true` | + +The `auth.*.clientId` values still gate whether a provider is wired into the account service, so +keep setting them in values even though the credentials come from the external Secret. + ## GitHub Integration Bidirectional sync of issues, PRs, and comments between Huly and GitHub. Requires a [GitHub App](https://docs.github.com/en/apps/creating-github-apps). @@ -244,10 +288,13 @@ kubectl logs deployment/ -n --tail=20 ### Secrets -All secrets are auto-generated if left empty. They persist across `helm upgrade` via Kubernetes secret lookup. +All secrets are auto-generated if left empty. They persist across `helm upgrade` via Kubernetes +secret lookup, which requires a live cluster connection — see +[Externally Managed Secrets (GitOps)](#externally-managed-secrets-gitops) for ArgoCD and Flux. | Key | Description | Default | |-----|-------------|---------| +| `secrets.existingSecret` | Use a Secret you manage; disables the chart-managed Secret | `""` | | `secrets.serverSecret` | Shared JWT signing secret | auto | | `secrets.storageConfig` | Full storage connection string override | auto | | `secrets.cockroachPassword` | CockroachDB password | auto | diff --git a/helm/huly/templates/_helpers.tpl b/helm/huly/templates/_helpers.tpl index ec20b0b..ea6c969 100644 --- a/helm/huly/templates/_helpers.tpl +++ b/helm/huly/templates/_helpers.tpl @@ -25,7 +25,11 @@ Create a default fully qualified app name. Secret resource name. */}} {{- define "huly.secretName" -}} -{{- printf "%s-secret" (include "huly.fullname" .) }} +{{- if .Values.secrets.existingSecret -}} +{{- .Values.secrets.existingSecret -}} +{{- else -}} +{{- printf "%s-secret" (include "huly.fullname" .) -}} +{{- end -}} {{- end }} {{/* diff --git a/helm/huly/templates/secret.yaml b/helm/huly/templates/secret.yaml index 1c8ba53..1815e98 100644 --- a/helm/huly/templates/secret.yaml +++ b/helm/huly/templates/secret.yaml @@ -1,3 +1,4 @@ +{{- if not .Values.secrets.existingSecret }} {{- $secretName := include "huly.secretName" . -}} {{- $existing := lookup "v1" "Secret" .Release.Namespace $secretName -}} {{- $hasExisting := not (empty $existing) -}} @@ -122,3 +123,4 @@ data: GITHUB_APP_PRIVATE_KEY: {{ .Values.githubIntegration.privateKey | b64enc | quote }} GITHUB_APP_WEBHOOK_SECRET: {{ .Values.githubIntegration.webhookSecret | b64enc | quote }} {{- end }} +{{- end }} diff --git a/helm/huly/values.yaml b/helm/huly/values.yaml index c562bad..8febf06 100644 --- a/helm/huly/values.yaml +++ b/helm/huly/values.yaml @@ -21,6 +21,21 @@ hulyRegistry: hardcoreeng # Secrets — leave empty to auto-generate (persists across upgrades) # ────────────────────────────────────────────────────────────── secrets: + # Use a Secret you manage instead of the chart-managed one. + # When set, the chart renders no Secret of its own and every service reads + # from the named Secret — required for GitOps tools (ArgoCD, Flux) that + # render manifests without cluster access, and for External Secrets + # Operator / Sealed Secrets / Vault. + # + # Required keys: SERVER_SECRET, CR_DB_URL, STORAGE_CONFIG, + # COCKROACH_PASSWORD (when cockroach.enabled=true), + # REDPANDA_SUPERUSER_PASSWORD (when redpanda.enabled=true), + # AIBOT_PASSWORD and OPENAI_API_KEY (when aibot.enabled=true), + # GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET, GITHUB_CLIENT_ID/ + # GITHUB_CLIENT_SECRET, OPENID_CLIENT_ID/OPENID_CLIENT_SECRET/ + # OPENID_ISSUER (per configured auth provider), + # GITHUB_APP_* (when githubIntegration.enabled=true) + existingSecret: "" # Shared JWT signing secret (auto-generated if empty) serverSecret: "" # Full storage config string override.