Authorization bypass / Access control
Description
The commit fixes an authorization check bug in K8s Dashboards folder permission verification. Previously the code checked the folder access subresource and interpreted CanEdit (mapping to folders:write) as the gate for creating dashboards. This excluded users who only had dashboards:create permission on the folder but lacked folders:write, effectively denying legitimate dashboard creation for those RBAC configurations. The patch replaces the folder subresource check with a direct dashboards:create permission check scoped to the target folder via the accessClient.Check mechanism. This aligns permission semantics across enterprise custom RBAC and OSS resource permission levels and ensures that dashboard creation is allowed when dashboards:create is granted, even if folders:write is not.
Proof of Concept
PoC outline (before vs after fix):
Prerequisites: Grafana instance with K8s Dashboards feature enabled; a folder with UID FOLDER_UID; a user U granted dashboards:create on the folder but not folders:write.
Pre-fix behavior (buggy path):
- User U attempts to create a dashboard in FOLDER_UID via Grafana API.
- The server checks the folder access subresource CanEdit (folders:write) and denies access if folders:write is not granted.
- Result: HTTP 403 Forbidden, dashboard creation blocked despite dashboards:create being granted.
Post-fix behavior (patched path):
- User U attempts the same create operation.
- The server performs a direct dashboards:create check scoped to the folder (instead of CanEdit on the folder subresource).
- If dashboards:create is granted, the operation succeeds.
- Result: HTTP 201 Created (or equivalent success response).
Concrete illustrative curl (illustrative only):
- Payload for creating a dashboard in a folder:
curl -X POST https://grafana.example/api/dashboards -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '{"dashboard":{"title":"Poc Test","uid":"poc-test-123","folderUid":"FOLDER_UID"}}'
- Expected outcome:
- If pre-fix: 403 Forbidden (due to CanEdit check failing).
- If post-fix: 201 Created (assuming dashboards:create is granted).
Notes: This PoC assumes the K8s Dashboards feature and RBAC configured to grant dashboards:create on the folder but not folders:write. The fix ensures permissions are evaluated against dashboards:create rather than the broader folders:write capability.
Commit Details
Author: Mihai Turdean
Date: 2026-05-14 18:55 UTC
Message:
K8s Dashboards: Fix folder permission check to use dashboards:create (#124612)
* K8s Dashboards: Fix folder permission check to use dashboards:create
The verifyFolderAccessPermissions method was checking CanEdit on the
folder access subresource, which maps to folders:write. This meant
enterprise users with custom RBAC roles granting only dashboards:create
+ dashboards:write on a folder were denied dashboard creation, because
they lacked folders:write.
Replace the folder access subresource fetch with a direct
accessClient.Check() call for VerbCreate on the dashboards resource
scoped to the target folder. This checks the semantically correct
permission (dashboards:create) and works for both enterprise custom
roles and OSS resource permission levels (Edit grants the folders:edit
action set which includes dashboards:create).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* K8s Dashboards: Simplify verifyFolderAccessPermissions signature
Both call sites pass a single folder UID, so the variadic parameter is
unnecessary. Take a single folderID string instead.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Triage Assessment
Vulnerability Type: Authorization bypass / Access control
Confidence: HIGH
Reasoning:
The commit changes the permission check from relying on a folder subresource (CanEdit) to a direct dashboards:create check, ensuring enterprise custom RBAC and OSS permissions correctly gate dashboard creation. This addresses an authorization bypass where users could be denied creation despite having dashboards:create on the folder. It fixes access control logic rather than introducing new features.
Verification Assessment
Vulnerability Type: Authorization bypass / Access control
Confidence: HIGH
Affected Versions: < 12.4.0
Code Diff
diff --git a/pkg/registry/apis/dashboard/register.go b/pkg/registry/apis/dashboard/register.go
index 3a9ea8a266df..144cc6772136 100644
--- a/pkg/registry/apis/dashboard/register.go
+++ b/pkg/registry/apis/dashboard/register.go
@@ -1102,34 +1102,24 @@ func (b *DashboardsAPIBuilder) GetAuthorizer() authorizer.Authorizer {
})
}
-func (b *DashboardsAPIBuilder) verifyFolderAccessPermissions(ctx context.Context, user identity.Requester, folderIds ...string) error {
+func (b *DashboardsAPIBuilder) verifyFolderAccessPermissions(ctx context.Context, user identity.Requester, folderID string) error {
ns, err := request.NamespaceInfoFrom(ctx, false)
if err != nil {
return err
}
- folderClient := b.folderClientProvider.GetOrCreateHandler(ns.Value)
-
- for _, folderId := range folderIds {
- resp, err := folderClient.Get(ctx, folderId, ns.OrgID, metav1.GetOptions{}, "access")
- if err != nil {
- if apierrors.IsNotFound(err) {
- return apierrors.NewNotFound(folders.FolderResourceInfo.GroupResource(), folderId)
- }
- if apierrors.IsForbidden(err) {
- return apierrors.NewForbidden(folders.FolderResourceInfo.GroupResource(), folderId, folder.ErrAccessDenied)
- }
- return err
- }
- var accessInfo folders.FolderAccessInfo
- err = runtime.DefaultUnstructuredConverter.FromUnstructured(resp.Object, &accessInfo)
- if err != nil {
- logging.FromContext(ctx).Error("Failed to convert folder access response", "error", err)
- return err
- }
- if !accessInfo.CanEdit {
- return apierrors.NewForbidden(folders.FolderResourceInfo.GroupResource(), folderId, folder.ErrAccessDenied)
- }
+ gvr := dashv1.DashboardResourceInfo.GroupVersionResource()
+ resp, err := b.accessClient.Check(ctx, user, authlib.CheckRequest{
+ Verb: utils.VerbCreate,
+ Group: gvr.Group,
+ Resource: gvr.Resource,
+ Namespace: ns.Value,
+ }, folderID)
+ if err != nil {
+ return err
+ }
+ if !resp.Allowed {
+ return apierrors.NewForbidden(folders.FolderResourceInfo.GroupResource(), folderID, folder.ErrAccessDenied)
}
return nil