Skip to content

fix(info): use root-host for Keycloak OIDC issuer URL in tenant kubeconfig - #2704

Merged
Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
fix/info-kubeconfig-oidc-issuer-url
May 26, 2026
Merged

Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
fix/info-kubeconfig-oidc-issuer-url

Conversation

@myasnikovdaniil

@myasnikovdaniil myasnikovdaniil commented May 21, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does

The kubeconfig generated by the info package for non-root tenants pointed --oidc-issuer-url at keycloak.<TENANT>.<ROOT-DOMAIN>, but Keycloak's ingress and cert live at keycloak.<ROOT-DOMAIN> (built from _cluster.root-host). kubectl oidc-login then failed TLS verification against the nginx-ingress default fake cert, so the auto-generated kubeconfig was unusable for any non-root tenant.

$host was being sourced from _namespace.host (tenant-specific subdomain) with a lookup-based override that tried to read tenant-root's spec.values.host. That override has been dead since #1787 migrated tenant-root to valuesFrom (Secret-backed) — spec.values is now always empty, so the override never fires.

Source $host from _cluster.root-host directly, matching what packages/system/dashboard/templates/gatekeeper.yaml already does for the same Keycloak realm.

Verified with helm template tenant1 packages/extra/info -n tenant-tenant1 --set-string _namespace.host=tenant1.example.org --set-string _cluster.root-host=example.org ... — issuer URL is now https://keycloak.example.org/realms/cozy regardless of the per-namespace host.

Release note

fix(info): use root-host for the Keycloak OIDC issuer URL in tenant kubeconfigs so `kubectl oidc-login` succeeds against the real Keycloak cert on non-root tenants

Summary by CodeRabbit

  • Chores
    • Updated kubeconfig generation to consolidate host configuration handling, now using a single authoritative source instead of multiple fallback mechanisms.

Review Change Stack

…onfig

The kubeconfig generated for non-root tenants pointed
`--oidc-issuer-url` at `keycloak.<TENANT>.<ROOT-DOMAIN>` because
`$host` was sourced from `_namespace.host` (tenant-specific
subdomain). Keycloak's ingress and cert live at
`keycloak.<ROOT-DOMAIN>` (built from `_cluster.root-host`), so
`kubectl oidc-login` failed TLS verification against the
nginx-ingress default fake cert.

The existing lookup-based override that tried to read
`tenant-root`'s `spec.values.host` no longer triggers since
PR #1787 migrated tenant-root to `valuesFrom` (Secret-backed),
leaving `spec.values` empty.

Use `_cluster.root-host` directly, matching what the dashboard
gatekeeper template already does for the same Keycloak realm.

Assisted-By: Claude <[email protected]>
Signed-off-by: Myasnikov Daniil <[email protected]>
@github-actions github-actions Bot added area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review kind/bug Categorizes issue or PR as related to a bug size/XS This PR changes 0-9 lines, ignoring generated files labels May 21, 2026
@coderabbitai

coderabbitai Bot commented May 21, 2026 •

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c7af69b9-7406-4389-b746-e4865d4b00a8

📥 Commits

Reviewing files that changed from the base of the PR and between ce8e738 and 65ce407.

📒 Files selected for processing (1)
  • packages/extra/info/templates/kubeconfig.yaml

📝 Walkthrough

Walkthrough

The kubeconfig Helm template is simplified to resolve the host exclusively from cluster-level configuration. The template removes fallback logic that previously defaulted to namespace values and eliminates a conditional lookup of Flux HelmRelease resources that could override the host.

Changes

Kubeconfig Host Resolution Simplification

Layer / File(s) Summary
Host resolution logic simplification
packages/extra/info/templates/kubeconfig.yaml
$host is assigned directly from Values._cluster["root-host"] without fallbacks. Conditional Flux HelmRelease lookup that could override $host is removed. Template proceeds linearly after defining $k8sCa.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Poem

🐰 The kubeconfig now stands tall and clear,
No tangled paths or fallbacks here,
Just cluster root from the single source,
A simpler flow, a cleaner course!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically identifies the main change: switching from using per-tenant namespace host to using root-host for the Keycloak OIDC issuer URL in kubeconfig templates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/info-kubeconfig-oidc-issuer-url

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@dosubot dosubot Bot added area/extra Issues or PRs related to tenant-specific modules (packages/extra/) kind/backport Categorizes issue or PR as requiring a backport to the current release line labels May 21, 2026
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves an issue where auto-generated kubeconfigs for non-root tenants were failing TLS verification during OIDC login. By standardizing the OIDC issuer URL to use the cluster's root-host, the configuration now correctly points to the valid Keycloak ingress, and redundant, non-functional lookup logic has been cleaned up to simplify the template.

Highlights

  • OIDC Issuer URL Fix: Updated the kubeconfig generation to use the root-host for the Keycloak OIDC issuer URL, ensuring compatibility with valid TLS certificates on non-root tenants.
  • Removed Stale Lookup Logic: Removed dead code that attempted to look up host configurations from HelmRelease values, which had become non-functional following previous architectural changes.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment Gemini (@gemini-code-assist) Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on Gemini (@gemini-code-assist) comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request simplifies the kubeconfig.yaml template by removing a FluxCD-specific lookup block and streamlining the $host variable assignment. The feedback suggests improving code clarity by moving the $host definition inside the OIDC conditional block and applying defensive programming practices to handle potentially missing keys in .Values._cluster.

@@ -1,4 +1,4 @@
{{- $host := .Values._namespace.host | default (index .Values._cluster "root-host") }}
{{- $host := index .Values._cluster "root-host" }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

The variable $host is now only used within the OIDC configuration block (line 38) and is no longer reassigned since the lookup block was removed. To improve code clarity and keep the scope tight, consider moving its definition inside the {{- if $oidcEnabled }} block, consistent with other OIDC-specific variables like $apiServerEndpoint and $k8sCa. Additionally, following the defensive programming guidelines in the style guide, it is safer to provide a default or check for the existence of _cluster (e.g., index (.Values._cluster | default dict) "root-host") to avoid template errors if the key is missing.

References
  1. Enforce defensive programming: ensure appropriate null/nil checks or other language-idiomatic guards exist before object property accesses. (link)

@lexfrei Aleksei Sviridkin (lexfrei) removed the area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review label May 25, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — surgical fix, +1/-7. The bug is real, the dead-code removal claim checks out, and the fix matches the established pattern elsewhere in the chart tree.

Verified:

  • Bug is real: pre-PR, $host defaulted to .Values._namespace.host (e.g. tenant1.example.org), so the rendered OIDC issuer URL was https://keycloak.tenant1.example.org/realms/cozy. But Keycloak's ingress and TLS cert live at keycloak.<root-host> (e.g. keycloak.example.org), so oidc-login hit nginx-ingress's default fake cert and failed TLS verification on every non-root tenant.
  • Cross-reference accurate: packages/system/dashboard/templates/gatekeeper.yaml uses exactly this shape — {{- $host := index .Values._cluster "root-host" }} followed by --oidc-issuer-url=https://keycloak.{{ $host }}/realms/cozy. The info kubeconfig now matches the same realm in the same way, so a tenant user and the dashboard both authenticate against the same issuer URL.
  • Dead-code claim accurate: tenant-root.yaml uses valuesFrom: Secret name: cozystack-values (verified in packages/system/cozystack-basics/templates/tenant-root.yaml), so tenantRoot.spec.values is the empty struct, the and $tenantRoot $tenantRoot.spec $tenantRoot.spec.values $tenantRoot.spec.values.host chain always short-circuits, and the override has been a no-op since #1787 (merged 2026-01-05) migrated tenants to the Secret-backed valuesFrom mechanism. Removing the block matches the post-#1787 reality.
  • Codex finds no regression. CI is green (Build + E2E + pre-commit + Verify generated all pass).

Bot finding is cosmetic, not a blocker:

  • Gemini (low): "$host is now only used inside the $oidcEnabled block; consider moving it inside." Pure style nit, can be deferred — keeping the host at the top mirrors the dashboard gatekeeper.yaml shape, which is consistent.

Non-blocking follow-up (not gating this fix): packages/extra/info/ has no tests/ directory, so the contract "OIDC issuer URL renders from _cluster.root-host, not _namespace.host" isn't pinned. Neighbouring extra packages do ship helm-unittest (packages/extra/{ingress,etcd,gateway}/tests/). Adding a minimal info/tests/kubeconfig_test.yaml that asserts the rendered Secret contains https://keycloak.<root-host>/realms/cozy and explicitly not the namespace host would pin this regression vector for next time. Out of scope for a 1-line backport-tagged fix; worth a separate task once the info package gets its test scaffolding.

@lexfrei
Aleksei Sviridkin (lexfrei) merged commit edb3eeb into main May 26, 2026
14 checks passed
@lexfrei
Aleksei Sviridkin (lexfrei) deleted the fix/info-kubeconfig-oidc-issuer-url branch May 26, 2026 10:41
Aleksei Sviridkin (lexfrei) added a commit that referenced this pull request Oct 7, 2026
…#3527)

## What this PR does

This adds `cmd/backport-audit`, a maintainer tool that checks, before a
release or an rc is cut, whether everything labelled for backport
actually landed on the release branch. It prints the URLs of the PRs
that did not and exits non-zero, so the cut can gate on it.
`docs/release.md` and `docs/agents/releasing.md` now send the patch-cut
check through it.

The docs it replaces inventoried backports with `gh pr list --search
"is:merged label:kind/backport"`. That lists every PR ever labelled, not
what reached the branch. Answering the real question by hand means
reading the branch history per PR, and that is how backports go missing
quietly.

```console
$ go build ./cmd/backport-audit
$ ./backport-audit release-1.4

=== release-1.4 === 30 candidate PRs: MISSING=5 dropped=4 backported=21

  MISSING -- labelled for this line, no trace of it here (5):
    #2704
      #2704 fix(info): use root-host for Keycloak OIDC issuer URL in tenant kubeconfig
      label=kind/backport author=myasnikovdaniil merged=2026-05-26 -- no backport PR, nothing on branch
```

A `kind/backport` label does not name a branch. `backport.yaml` resolves
the target when the PR merges, so the same label means `release-1.5` on
a PR merged in June and `release-1.6` on one merged in August. The audit
reproduces that rule instead of trusting the label text. The rule
changed once, on 2026-08-03, when 53a7fc4 made cutting an rc freeze
the line and 6ff5b98 pointed the bot at the branch list. Before that
date a line exists from its first stable release, after it from the
moment its `release-X.Y` branch is pushed. The audit estimates that
moment from the earliest `vX.Y.0-rc.N` tagged commit. This is an
estimate, because the commit timestamp is not the branch-push timestamp.
The README has the full reasoning.

Landing is checked per commit, because a commit is what a backport
loses: the bot's conflict drafts stop at the first commit that does not
apply. Every non-empty commit of the original needs its own evidence on
the branch. That is reachability, an `-x` reference, or a commit with
its subject among the branch commits that are not on main. Some but not
all is PARTIAL. A multi-commit original with a merged backport PR and no
commit evidence is UNVERIFIED. Both fail the gate. A maintainer can
confirm a squashed backport with a comment on the merged backport PR
that is exactly `backport-audit: complete`.

Two more sections start from the backport PRs on the branch instead of
from labels. DUPLICATE lists an original with more than one live
backport PR and fails the gate. UNLABELLED lists backports of originals
that are not labelled for this line. It is informational and does not
change the exit code.

Exit code 2 means the audit could not give an answer. That happens when
a `gh` listing reaches its cap, since `gh` truncates silently, or when a
git read that a verdict depends on fails. The label listings are checked
against the lower of `--limit` and 1000, because GitHub search never
returns more than 1000 results.

### Verification

`cmd/backport-audit/main_test.go` covers candidate selection, line
resolution and the evidence rules. `make unit-tests` runs it through a
new `test-backport-audit` target, since `./cmd/...` is not part of
`go-unit-tests`. `hack/release-freeze-contract.bats` pins the audit's
label set equal to the one `backport.yaml` acts on, in both directions.

### Limits

A MISSING verdict is a prompt to check, not proof of absence. A hand
backport that was squashed, reworded and never linked to its original is
invisible to every evidence layer. A label added after merge is read
against the line that was current at merge time, while the bot targets
the line current when the label is added. Such a backport shows as
MISSING on the old line and under UNLABELLED on the new one. The audit
reports that something is missing, never why. `docs/release.md` now
notes that before #3155 the bot left only a "Backport failed" comment on
a conflicting cherry-pick, which is the first thing to check behind a
MISSING.

`make generate` does not apply. This is a standalone `main` package with
no API types, no CRDs and no package `values.yaml`.

### Screenshots

Not a UI change. Sample CLI output is above.

### Downstream repositories

I walked the trigger map in `docs/agents/contributing.md` against the
diff file by file. The diff adds
`cmd/backport-audit/{main.go,main_test.go,README.md}` and changes
`Makefile`, `hack/release-freeze-contract.bats`, `docs/release.md`,
`docs/agents/releasing.md` and one `.gitignore` line for the root
binary.

Nothing in it reaches a downstream repository. No package under
`packages/apps/` or `packages/extra/` is added, renamed or removed, and
no `values.schema.json`, `values.yaml` default or version enum changes,
so the website generator lists and the terraform provider schemas are
untouched. No `ApplicationDefinition` semantics, `release.prefix`,
output Secret or Service name, namespace, platform variant or bundle
changes. The paths that reach ccp and external-apps-example
(`hack/package.mk`, `hack/common-envs.mk`, `hack/update-crd.sh`) are not
touched. The Makefile change adds a `test-backport-audit` target to
`unit-tests` and changes no existing target. No node prerequisite
changes, so talm and the ansible playbooks need nothing. The website
"developer tooling" trigger covers tools contributors use on packages.
This is maintainer release tooling, and its process doc lives in this
repository only.

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

### Release note

```release-note
feat(release): add `backport-audit`, a maintainer tool that reports which `kind/backport` / `kind/backport-previous` PRs have not landed on a release branch (missing outright, partially backported, waiting on an open backport PR, or deliberately dropped) and exits non-zero while anything is outstanding, so a patch cut can gate on it
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/extra Issues or PRs related to tenant-specific modules (packages/extra/) kind/backport Categorizes issue or PR as requiring a backport to the current release line kind/bug Categorizes issue or PR as related to a bug size/XS This PR changes 0-9 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants