[sec-check] fix: remove hardcoded kubestellar console secrets and auto-applied manifest - #90
Conversation
…ed manifest Delete files/k0s/manifests/kubestellar/01-kubestellar-console-github-oauth.yaml so no hardcoded JWT secrets or dummy OAuth credentials are automatically seeded into /var/lib/k0s/manifests/ or applied by the k0s manifest deployer. Make JWT_SECRET secretKeyRef in 40-kubestellar-console.yaml optional so the console starts cleanly in local development mode without requiring a pre-seeded Secret manifest (in-container console startup generates an ephemeral secret when unset). Update unit test contracts in test_k0s_manifests.py and test_kubestellar_kiosk.py to assert the secret manifest is not present and all secret references are optional. Document operator-provided OAuth Secret configuration in docs/skills/k0s-sysext-ops.md. Closes projectbluefin#72 Signed-off-by: Danathar <Danathar@users.noreply.github.com>
hanthor
left a comment
There was a problem hiding this comment.
Good fix. Two things to confirm before it lands, and one much larger issue in the same file.
Deleting 01-kubestellar-console-github-oauth.yaml is clearly right — jwt-secret: default-jwt-secret-123456789012345678901234 was a fixed signing key auto-applied on every default install, and shipping it in the sysext meant every Bluefin Server signed tokens with the same value. Removing it is the correct call.
I checked the obvious way this could break and it does not: deleting that file does not orphan the namespace, because 40-kubestellar-console.yaml declares it itself.
$ git show pr90:files/k0s/manifests/kubestellar/40-kubestellar-console.yaml | head -4
apiVersion: v1
kind: Namespace
metadata:
name: kubestellar-console
So the Service and Deployment still have a namespace to land in. Good.
1. The central safety claim is not verifiable from this repo
The body states:
When unset, the console container automatically generates an ephemeral random 32-byte secret at startup (
console/start.sh).
console/start.sh is not in this repository:
$ git ls-tree -r --name-only pr90 | grep -i 'start.sh'
(no output)
It lives inside ghcr.io/kubestellar/console:v0.3.34. The whole security argument for optional: true rests on that behaviour. If the image instead falls back to an empty or compiled-in default signing key when JWT_SECRET is unset, this change makes forgery easier than the status quo, because the bad-but-known secret is replaced by a bad-and-silent one. Please link the upstream start.sh line, or pin the behaviour with a comment in the manifest, so a future image bump can't quietly invalidate it.
2. The console image is on a mutable tag
image: ghcr.io/kubestellar/console:v0.3.34No digest. This is the same class of issue #81 is fixing for argocd, and the kiosk proxy in the sibling file is already pinned (nginx@sha256:62223d644fa234c3a1cc785ee14242ec47a77364226f1c811d2f669f96dc2ac8). Since this PR is already editing this file for supply-chain reasons and the behaviour in item 1 is image-dependent, pinning by digest here would be cheap and would make the claim durable.
3. The bigger hole is three lines above the one you changed
Unchanged by this PR, but in the same container spec:
env:
- name: DEV_MODE
value: "true"
- name: ALLOW_DEV_MODE_IN_CLUSTER
value: "true"
- name: DEV_USER_LOGIN
value: "admin"To be explicit about baseline: this is pre-existing on main, not introduced here. But it substantially undercuts what this PR achieves. Hardening the JWT signing key matters only if authentication is enforced; with dev mode explicitly enabled in-cluster and a hardcoded admin login, anyone who reaches the console is already an administrator, regardless of how the JWT is signed. Worth a follow-up issue — and it's arguably the finding that should be at the top of #72's queue.
CI status
No checks have run on this PR at all:
mcp__github__pull_request_read(method="get_check_runs", pullNumber=90)
-> {"total_count":0,"check_runs":[]}
mergeable_state is unstable; it's a fork PR (Danathar/server) and looks like it needs maintainer approval for workflows to run. I ran the gates locally on main + this branch: 201 passed, 1 xfailed, 41/41 bats, all three .github/scripts checkers exit 0 — identical to the main baseline at 7f376fa. Nothing regressed. The two new assertions are cheap and correctly aimed.
Merge-order note
This and #91 both edit docs/skills/k0s-sysext-ops.md and tests/unit/test_kubestellar_kiosk.py. Each merges cleanly into main alone, but not into each other:
$ git merge pr90 # onto main+pr91
CONFLICT (content): Merge conflict in docs/skills/k0s-sysext-ops.md
Whichever goes second needs a rebase.
Marking this as a comment rather than a block — items 1 and 2 are worth resolving first, but the change itself is an improvement over what's on main.
Generated by Claude Code
hanthor's review on projectbluefin#90 confirmed the optional: true change is correct but flagged two things to resolve first: 1. The safety argument ("the console generates an ephemeral random secret when JWT_SECRET is unset") rests on upstream behaviour this repo can't see. Fetched kubestellar/console's start.sh at the exact pinned tag (v0.3.34) and confirmed it directly: when JWT_SECRET is unset, it generates `openssl rand -hex 32` (falling back to /dev/urandom, never an empty or compiled-in default), persists it to $INSTALL_DIR/data/.jwt_secret so it survives a plain container restart, and exports it. Since this Deployment mounts no volume for that path, it's lost on pod recreation -- so "ephemeral" is accurate at the pod-lifetime granularity, just not on every single restart. Added a comment citing the exact upstream lines so a future image bump that changes this can't invalidate the claim silently. 2. The image was on a mutable tag while the sibling kiosk-proxy nginx image in the same manifest set is already digest-pinned. Resolved ghcr.io/kubestellar/console:v0.3.34's current digest via skopeo (confirmed it's an OCI image index, same multi-arch-safe shape as a tag reference) and pinned it. Left item 3 (DEV_MODE/admin login already on main) alone, per the review's own framing -- pre-existing, out of scope, worth a follow-up issue rather than scope creep here. Verified: all 8 relevant unit tests pass, full suite 201/201 + 1 xfailed, YAML parses clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LNgJ8VMdoqzGequbxsP2Kc
|
@hanthor — pushed in
Left item 3 alone per your own framing — pre-existing on Verified: all 8 relevant unit tests pass, full suite 201/201 + 1 xfailed. On the merge-order note with #91: pushed both independently; whichever lands second will need the small rebase on |
Security Fix
Addresses finding in #72:
files/k0s/manifests/kubestellar/01-kubestellar-console-github-oauth.yamlcommitted dummy GitHub OAuth credentials and a static JWT secret that was automatically seeded into/var/lib/k0s/manifests/and deployed by the k0s manifest deployer on default installations.Changes
files/k0s/manifests/kubestellar/01-kubestellar-console-github-oauth.yamlso no hardcoded secrets or dummy credentials are baked into the sysext or auto-applied by k0s.JWT_SECRETOptional in Console Deployment: Infiles/k0s/manifests/kubestellar/40-kubestellar-console.yaml, setoptional: trueon theJWT_SECRETSecret reference (matching the existingoptional: trueonclient-idandclient-secret). When unset, the console container automatically generates an ephemeral random 32-byte secret at startup (console/start.sh).tests/unit/test_k0s_manifests.pyto assert01-kubestellar-console-github-oauth.yamlis not present in manifests, and updatedtests/unit/test_kubestellar_kiosk.pyto verify all Secret references are optional.docs/skills/k0s-sysext-ops.md.Closes #72
— hive: backend=agy