Files
cozystack/packages/system/monitoring/images
IvanHunters 01d68904ea feat(monitoring): drop chart-owned Keycloak groups; app-side users-map + reconcile Job
Replace the group-based authorization model on the Phase-1 Grafana
OIDC selector with an app-side users map, mirroring the tenant
kube-apiserver sibling in cozystack/cozystack#3044.

What is removed
- The three chart-owned KeycloakRealmGroup objects in the cozy realm
  (`<clientId>-{admin,editor,viewer}`). A Helm release must not own
  directory objects — toggling `spec.oidc.mode: System -> None` or
  deleting the Monitoring release would Helm-delete the groups, and
  with them any membership curated out-of-band. Per-instance groups
  in the platform realm is exactly the design question left open at
  the end of cozystack/community#24; establishing it as precedent
  from a monitoring PR is out of scope.
- Dash-separated group naming (`<namespace>-<release>-<role>`) was
  also collision-prone against tenant/subtenant paths, since both
  the namespace and release names can themselves contain dashes.
- `role_attribute_path` in the Grafana `generic_oauth` block and the
  default-Viewer fallback, which handed org-Viewer to every cozy
  realm identity that logged in — cross-tenant metrics read across
  every opted-in Grafana. Also removed the `groups` client scope
  from the KeycloakClient defaults.
- `monitoring.oidc.allowAssignGrafanaAdmin` helper and the internal
  `oidc.grafanaAdmin` values key: the feature was already deferred
  and the helper's release-name-only guard was tenant-spoofable.

What replaces it
- `spec.oidc.users: [{email, role: Admin|Editor|Viewer}]` on the
  Monitoring chart values.
- A chart-owned post-install/post-upgrade Job
  (`templates/grafana/oidc-users-job.yaml`) that reconciles that
  list into Grafana's Main Org. via the admin API: pre-provisions
  a local Grafana account per listed email so the reconcile can
  set the org role before the operator's first OIDC login, adds
  the account to the org, PATCHes the role to converge across
  re-runs, and prunes every non-admin Main-Org member whose email
  is not in the list (handles both entry removal and `mode: None`).
- `skip_org_role_sync = true` + `oauth_allow_insecure_email_lookup
  = true` in the generic_oauth block. The former stops a login from
  overwriting the Job's assignments; the latter binds the OIDC
  identity to the pre-provisioned local account.
- Job talks to Grafana over the in-cluster Service and reads
  admin creds from the chart-managed `grafana-admin-password`
  Secret via `envFrom`: no ServiceAccount / Role / RoleBinding.
  `activeDeadlineSeconds: 900` caps a stuck Job so a broken Grafana
  cannot hold the release in `pending-upgrade`.

Defensive rendering
- `.Values.oidc | default dict` in the four templates that dereference
  the `oidc` block so `oidc: ~` or an omitted block no longer nil-derefs.
- `oidc-client-secret.yaml` falls back to a fresh random when the
  existing Secret lookup succeeds but `data.client-secret` is empty.

Rationale: cozystack/cozystack#3176 review from @lllamnyp
(architectural CHANGES_REQUESTED points 1, 2, 3 + the default-Viewer
leak). The three points stack: dropping the groups incidentally
fixes the naming-collision and the default-Viewer leak, and the
users-Job gives operators the same UX story as the tenant
kube-apiserver PR across both sides of the platform.

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2026-07-15 15:31:45 +03:00
..