Skip to content

make hub.existingSecret fully replace the chart-generated secrets - #3903

Merged
minrk merged 7 commits into
jupyterhub:mainfrom
erwinschliske:fix/hub-existing-secret-full-replacement
Oct 21, 2026
Merged

minrk merged 7 commits into
jupyterhub:mainfrom
erwinschliske:fix/hub-existing-secret-full-replacement

Conversation

@erwinschliske

@erwinschliske erwinschliske commented May 28, 2026 •

Copy link
Copy Markdown
Contributor

When hub.existingSecret is set, the chart still renders its own hub Secret
with randomly generated tokens. In ArgoCD/GitOps setups where lookup() has
no cluster access, these tokens change on every render, which rolls the hub pod
on every reconcile.

This PR makes hub.existingSecret actually replace the token keys:

  • Omit the random token keys from the hub Secret when existingSecret is set (values.yaml, db.password and extraFiles are still rendered — those are deterministic and needed for volume mounts)
  • Point CONFIGPROXY_AUTH_TOKEN in hub and proxy Deployments to the user-managed Secret
  • The checksum/secret annotation now stabilises because the hub Secret no longer contains random values

The hub-existing-secret-or-default helper already existed — it just wasn't
wired up in the right places.

No change when hub.existingSecret is not set.

When hub.existingSecret is set, the chart managed hub Secret is no
longer rendered. Hub and proxy pods read CONFIGPROXY_AUTH_TOKEN and
all other tokens directly from the user managed Secret via the
existing jupyterhub.hub-existing-secret-or-default.fullname helper.

The checksum/secret annotation on the hub Deployment is omitted when
hub.existingSecret is set, preventing spurious rolling restarts caused
by non-deterministic token generation during Helm rendering without
cluster access (e.g. ArgoCD, helm diff).

Previously hub.existingSecret only supplemented the chart managed
Secret at runtime (via the /existing-secret/ volume mount), while
CONFIGPROXY_AUTH_TOKEN was always sourced from the chart managed
Secret. This meant the chart still generated random tokens on every
render, defeating the purpose of providing stable tokens via an
external Secret.

The user managed Secret must contain:
  hub.config.ConfigurableHTTPProxy.auth_token
  hub.config.JupyterHub.cookie_secret
  hub.config.CryptKeeper.keys
  hub.services.<name>.apiToken  (one per configured service)
The 'secret' volume in the hub Deployment previously always referenced
the chart-managed Secret by name. When hub.existingSecret is set and the
chart-managed Secret is not rendered, the volume mount would fail because
the referenced Secret no longer exists.

Use the hub-existing-secret-or-default.fullname helper so the secret
volume resolves to the user-managed Secret when hub.existingSecret is set.
Skipping the entire hub Secret broke hub.extraFiles: the files volume
mounts Secret content that the chart generates, which is unrelated to
the random token problem.

Instead, only guard the token keys (auth_token, cookie_secret,
CryptKeeper.keys, service apiTokens) with the existingSecret condition.
The Secret is still rendered with values.yaml, db.password and
extraFiles content — all deterministic. The checksum/secret annotation
now stabilises when existingSecret is set.

Also revert the secret volume secretName to hub.fullname; the chart
Secret is always present so the -or-default helper is not needed there.
When hub.existingSecret is set, the chart no longer writes service
apiTokens to the chart-managed Secret. The test infrastructure reads
these from the user-managed Secret, so they must be present there.
@erwinschliske

Copy link
Copy Markdown
Contributor Author

Closes #2887

test-explicit-name and test-generation-of-apiToken were also missing.
The checksum/auth-token annotation is computed from the auth token value
via a helper that falls back to randAlphaNum when lookup() returns nil.
In GitOps tools like ArgoCD, lookup() always returns nil, so the
checksum changes on every render and rolls the proxy pod on every
reconcile.

When hub.existingSecret is set the user manages the auth token outside
the chart. Drop checksum/auth-token in that case so the proxy is only
restarted by actual pod spec changes. Users who want automatic restarts
on token rotation can use Stakater Reloader or similar tools.
@minrk

minrk commented Aug 13, 2026

Copy link
Copy Markdown
Member

Sorry for the delay!

I think this makes sense. It is a breaking change because the docs say it will be merged and given highest priority, whereas this changes it to require being exclusively used, so prior existingSecrets that set a subset of keys used to work and will no longer work after this.

I think that's okay, we just need to update the schema so this change shows up in documentation.

@mauriziomartino-px

Copy link
Copy Markdown

I have been having an issue with exactly this behaviour and would love to see this released. @erwinschliske I am happy to update the docs if you don't have time, as this is quite urgent for us.

@minrk minrk changed the title fix: make hub.existingSecret fully replace the chart managed Secret make hub.existingSecret fully replace the chart-generated secrets Oct 21, 2026
@minrk
minrk merged commit 2d52210 into jupyterhub:main Oct 21, 2026
14 of 16 checks passed
consideRatio pushed a commit to jupyterhub/helm-chart that referenced this pull request Oct 21, 2026
jupyterhub/zero-to-jupyterhub-k8s#3903 Merge pull request #3903 from erwinschliske/fix/hub-existing-secret-full-replacement
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants