Skip to content

Dashboard migration forms return 403 for non-cluster-admin users: no role grants the forklift CRD schemas #4274

Description

Describe the bug

The migration create and edit forms build themselves from the CRD object, exactly as the backup forms in #4053 do, and no role in the tree grants read access to the two CRDs they ask for. The forms fail with a 403 for every user who is not a cluster admin.

MigrationSourceCreatePage.tsx, MigrationSourceEditPage.tsx and MigrationTaskCreatePage.tsx all call useCRDSchema, which issues a by-name get against apiextensions.k8s.io at cluster scope with the logged-in user's own token. The names they ask for are vmimportsources.forklift.cozystack.io and vmimporttasks.forklift.cozystack.io.

Nothing grants those. cozy:migration:view grants the forklift.cozystack.io resources themselves, which is a different API group from apiextensions.k8s.io, and a repo-wide search for either CRD name under packages/ returns no rule naming them.

Why this is filed separately from #4053

#4236 fixes the backup half by adding the five backup CRD names to cozystack-dashboard-readonly in packages/system/dashboard/templates/rbac.yaml, which is bound to system:authenticated by a ClusterRoleBinding in the same file. That is the carrier that works, because a cluster-scoped request is authorized against ClusterRoleBindings and every tenant role in this repository is bound by a namespaced RoleBinding instead.

The migration forms were outside that PR's scope and still have no grant.

Expected behaviour

A user who can already create a migration source or task can open the form for it.

Suggested fix

Add the two names to the same rule #4236 introduces. It already carries resourceNames and stops at get, which is the right verb: the hook does a by-name get, and a rule carrying resourceNames cannot authorize a list at all.

The same tradeoff applies as for the backup names: that role reaches every authenticated user rather than the members of one tenant. That is the standing shape for this role, which already holds storageclasses, applicationdefinitions and backupclasses on the same terms.

Worth knowing before implementing

Do not reach for a ClusterRole labelled rbac.cozystack.io/aggregate-to-tenant-view. That is what #4053 originally proposed and what the first revision of #4236 implemented, and it cannot work: the rule renders, aggregates correctly, and never reaches the authorizer, because cluster-scoped requests are not authorized through a RoleBinding.

A render test over the template would pass either way, since the defect is in the binding rather than the render. packages/system/dashboard/tests/cluster_usage_rbac_test.yaml is the same-package precedent that pins a cluster-scoped grant together with its binding's subject, and an assertion in that shape would catch it.

Activity

  1. added
    kind/bugCategorizes issue or PR as related to a bug
    area/dashboardIssues or PRs related to the dashboard / UI
    triage/acceptedIndicates an issue is ready to be actively worked on
    priority/important-soonMust be staffed and worked on either currently, or very soon, ideally in time for the next release
    and removed
    triage/needs-triageIndicates an issue needs triage by a maintainer
    on Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/dashboardIssues or PRs related to the dashboard / UIkind/bugCategorizes issue or PR as related to a bugpriority/important-soonMust be staffed and worked on either currently, or very soon, ideally in time for the next releasetriage/acceptedIndicates an issue is ready to be actively worked on

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions