Skip to content

feat(rbac): scope OIDC kubectl/Headlamp access to read-only#1657

Open
devantler wants to merge 2 commits into
mainfrom
claude/elastic-varahamihira-5f9b4f
Open

feat(rbac): scope OIDC kubectl/Headlamp access to read-only#1657
devantler wants to merge 2 commits into
mainfrom
claude/elastic-varahamihira-5f9b4f

Conversation

@devantler
Copy link
Copy Markdown
Contributor

Why

OIDC access (Dex + GitHub) was bound to cluster-admin via the oidc-admin ClusterRoleBinding — so any browser login (kubectl and Headlamp) became full admin. This makes day-to-day OIDC access read-only (least privilege), and reserves admin for break-glass via the root client-certificate kubeconfig kept in the vault.

This is Part A of moving the operator's daily prod credentials to dev-friendly, OIDC-backed, read-only ones. Part B (swapping the local ~/.kube/config to OIDC-only and generating an os:reader ~/.talos/config) is a local-workstation step, done after this is reconciled and verified — intentionally not in this PR.

What changed

  • New cluster-roles/cluster-reader.yaml — a ClusterRole for the cluster-scoped resources the built-in view role omits: nodes, PVs, storage classes, CRDs, API services, priority/runtime/ingress classes, CSRs, RBAC objects, and node/pod metrics (kubectl top). Excludes Secrets and every write/exec verb (get/list/watch only).
  • Replaced cluster-role-bindings/oidc-admin.yaml (→ cluster-admin) with oidc-readonly.yaml, two bindings for User: oidc:${admin_email}:
    Binding Role Grants
    oidc-view built-in view namespaced resources incl. pod logs/events — not Secrets
    oidc-cluster-reader cluster-reader cluster-scoped infra (above)
  • Docs: docs/oidc-kubectl.md updated for the read-only model + a Break-glass admin access section.

flux-web-admins (the limited Flux-UI binding) is unchanged.

Security model after this change

  • Daily (OIDC): read-only. No Secrets, no writes. Headlamp becomes a read-only console.
  • Break-glass (admin): the root cert kubeconfig/talosconfig from the vault — pulled only when a write is genuinely needed.

Design notes

  • roleRef is immutable, so changing the role is a delete + recreate of the binding (Flux prunes oidc-admin, applies the new ones). The cert-based admin path is untouched, so there is no lockout window during rollout.
  • Kept explicit and contained: bound alongside the built-in view rather than mutating view globally via aggregation, so no other subject's permissions change.
  • The one place we go beyond view's conservative defaults is read access to RBAC objects — invaluable for diagnosing Forbidden errors, and exposes no secret material. Easy to drop if undesired.

Rollout / safety

  1. Merge → CI/CD deploys to prod → Flux reconciles the new bindings (no root creds needed to apply).
  2. Verify read-only via the OIDC context (kubectl auth whoami, auth can-i --list, get secrets → Forbidden) while still holding the cert admin as fallback.
  3. Only then proceed to Part B (local file swap).

Validation

  • kubectl kustomize builds clean for both providers/hetzner/infrastructure and providers/docker/infrastructure.
  • ksail workload validate and ksail --config ksail.prod.yaml workload validate257 files validated, both.

🤖 Generated with Claude Code

OIDC (Dex + GitHub) was bound to cluster-admin via the oidc-admin
ClusterRoleBinding, so any browser login became full admin. Make
day-to-day OIDC access least-privilege (read-only) and keep admin as
break-glass via the root client-certificate kubeconfig stored in the
vault.

- Add cluster-reader ClusterRole: cluster-scoped infra reads (nodes,
  PVs, storage, CRDs, API services, CSRs, RBAC objects, node/pod
  metrics). Excludes Secrets and all write/exec verbs.
- Replace oidc-admin (cluster-admin) with two read-only bindings:
  oidc-view (built-in view) + oidc-cluster-reader (cluster-reader),
  both for User oidc:${admin_email}.
- Update docs/oidc-kubectl.md: read-only model + break-glass procedure.

roleRef is immutable, so this is a delete+recreate of the binding. The
cert-based admin path is untouched, so there is no lockout window.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR changes the RBAC model for Dex/GitHub OIDC logins so day-to-day access (kubectl + Headlamp) is read-only, and reserves cluster-admin for break-glass via the root client-certificate kubeconfig stored in the vault.

Changes:

  • Added a new cluster-reader ClusterRole to cover cluster visibility gaps not provided by the built-in view role (nodes, PVs, CRDs, API services, classes, metrics, RBAC objects, etc.) with get/list/watch only.
  • Replaced the oidc-admin (cluster-admin) ClusterRoleBinding with two read-only bindings: oidc-view (built-in view) + oidc-cluster-reader (cluster-reader).
  • Updated OIDC kubectl documentation to reflect the read-only + break-glass workflow.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
k8s/bases/infrastructure/cluster-roles/kustomization.yaml Registers the new cluster-reader role in the cluster-roles base.
k8s/bases/infrastructure/cluster-roles/cluster-reader.yaml Introduces the read-only cluster-reader ClusterRole covering additional visibility resources.
k8s/bases/infrastructure/cluster-role-bindings/kustomization.yaml Switches the base to apply oidc-readonly.yaml instead of oidc-admin.yaml.
k8s/bases/infrastructure/cluster-role-bindings/oidc-readonly.yaml Adds read-only bindings (view + cluster-reader) for the OIDC user identity.
k8s/bases/infrastructure/cluster-role-bindings/oidc-admin.yaml Removes the cluster-admin binding for OIDC identity.
docs/oidc-kubectl.md Updates instructions and adds break-glass admin guidance consistent with the new RBAC model.

Comment thread k8s/bases/infrastructure/cluster-roles/cluster-reader.yaml Outdated
Comment thread k8s/bases/infrastructure/cluster-role-bindings/oidc-readonly.yaml Outdated
@devantler devantler marked this pull request as ready for review May 29, 2026 13:37
Address Copilot review on #1657: the cluster-reader ClusterRole and its
binding were described as "cluster-scoped infra", but they also grant a
few namespaced resources cluster-wide (RBAC roles/rolebindings and pod
metrics). Reword the header comment, the binding comment, and the docs
RBAC table to say "what `view` omits — mostly cluster-scoped, plus RBAC
objects and pod metrics". No rule changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@botantler botantler Bot enabled auto-merge May 29, 2026 13:46
@botantler botantler Bot added this pull request to the merge queue May 29, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 29, 2026
@devantler devantler added this pull request to the merge queue May 29, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 29, 2026
@devantler devantler added this pull request to the merge queue May 29, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 29, 2026
@devantler devantler added this pull request to the merge queue May 29, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: 🫴 Ready

Development

Successfully merging this pull request may close these issues.

2 participants