Authorization

HIGH
grafana/grafana
Commit: c6c7f9ab9046
Affected: Grafana 12.4.0 (and 12.4.x series that include this translation layer)
2026-06-08 15:44 UTC

Description

Summary of the issue: - The commit fixes an authorization propagation gap in the Zanzana translation layer by adding missing mappings for permission-management actions (folders.permissions and dashboards.permissions). - Prior to this change, granting permission-management actions could be ignored by the translation logic, causing get_permissions/set_permissions relations not to be created for folders and dashboards. This could lead to improper access control or privilege escalation because permission changes were not propagated to the underlying authorization relations. What the fix does: - Adds mappings for folders.permissions:read/write and dashboards.permissions:read/write to translate into the appropriate OpenFGA relations (get_permissions / set_permissions) either at the folder level or scoped within the dashboard-subresources as appropriate. - Extends tests to cover the new translations, including both per-resource and scoped (folder/dashboard) cases. Impact: - Restores proper propagation of permission-management actions to the underlying permission relations, ensuring that granting those actions correctly updates get_permissions/set_permissions tuples. - This is a targeted security fix for authorization propagation and is accompanied by tests validating the mappings.

Proof of Concept

Proof-of-concept (conceptual steps you can adapt to your deployment): Prerequisites: - Grafana instance with OpenFGA/Zanzana-based authorization (as in Grafana 12.4.x) and a resource hierarchy including folders and dashboards. - A user account (e.g., user:alice) and a folder or dashboard resource (e.g., fold1 or dashboards under a folder). - The system should support permission-management actions such as folders.permissions:read and folders.permissions:write (and their dashboard equivalents). Steps to reproduce (pre-fix behavior would lack propagation): 1) Grant a permission-management action to a user for a resource: - Action: folders.permissions:read (or folders.permissions:write) - Target: a folder (e.g., fold1) or a folder’s dashboards group (depending on scope) - This should enqueue a translation that propagates to underlying permission relations. 2) Check whether the user gains the corresponding get_permissions/set_permissions tuple on the underlying object: - Expected (post-fix): a tuple such as { user: "user:alice", relation: "get_permissions", object: "folder:fold1" } (or the equivalent group_resource object, depending on scope: e.g., group_resource:folder.grafana.app/folders). - And for write permissions: a tuple with relation set_permissions on the appropriate object. 3) Validate authorization behavior: - Query the policy engine to see if user:alice can get_permissions on folder:fold1 (or the grouped resource) after the grant. - Before the fix, this check would likely fail because the get_permissions tuple was not created due to the missing translation mapping. - After applying this fix, the check should pass, indicating proper propagation of the permission-management action. OpenFGA/OpenAPI-style example calls (adapt to your deployment): # 1) Grant permission-management action to user curl -X POST 'https://fga.example.org/stores/grafana/write/tuples' \ -H 'Content-Type: application/json' \ -d '{"tuples":[{"user":"user:alice","relation":"folders.permissions:read","object":"group_resource:folder.grafana.app/folders"}]}' # 2) Check for propagated get_permissions relation on the folder curl -X POST 'https://fga.example.org/stores/grafana/read/tuples/check' \ -H 'Content-Type: application/json' \ -d '{"tuple":{"user":"user:alice","relation":"get_permissions","object":"group_resource:folder.grafana.app/folders"}}' Expected: true after the fix; false before the fix (demonstrating the vulnerability existed).

Commit Details

Author: Mihai Turdean

Date: 2026-06-08 15:03 UTC

Message:

Zanzana: Translate folder and dashboard permission actions to tuples (#125926) The resource translation table mapped folders/dashboards read/write/create/ delete and the view/edit/admin action sets, but not the permission-management actions (folders.permissions:read/write, dashboards.permissions:read/write). Those fell through to the "can't translate" branch and never produced a Zanzana tuple, so granting them to a role didn't propagate the corresponding get_permissions/set_permissions relation. Add the missing mappings for both the folders kind (folder + dashboard-in- folder subresource scoping) and the dashboards kind. The FGA schema already defines get_permissions/set_permissions (and the resource_-prefixed subresource variants) on the folder, resource, and group_resource types, so no schema change is needed. Adds tests covering the new translations. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

Triage Assessment

Vulnerability Type: Authorization

Confidence: HIGH

Reasoning:

The commit adds mappings for permission-management actions (folders.permissions and dashboards.permissions) to the Zanzana translation layer, ensuring get_permissions/set_permissions relations are propagated correctly when granting those permissions. This fixes an authorization propagation gap where permission actions could be ignored, potentially leading to improper access control or privilege escalation. The changes affect access control translation logic and include tests, indicating a targeted security fix rather than a pure refactor or documentation change.

Verification Assessment

Vulnerability Type: Authorization

Confidence: HIGH

Affected Versions: Grafana 12.4.0 (and 12.4.x series that include this translation layer)

Code Diff

diff --git a/pkg/services/authz/zanzana/common/translations.go b/pkg/services/authz/zanzana/common/translations.go index 8094231d6483c..25175852d25c4 100644 --- a/pkg/services/authz/zanzana/common/translations.go +++ b/pkg/services/authz/zanzana/common/translations.go @@ -78,6 +78,11 @@ var resourceTranslations = map[string]resourceTranslation{ "dashboards:write": newScopedMapping(RelationUpdate, dashboardGroup, dashboardResource, ""), "dashboards:create": newScopedMapping(RelationCreate, dashboardGroup, dashboardResource, ""), "dashboards:delete": newScopedMapping(RelationDelete, dashboardGroup, dashboardResource, ""), + // Permission management + "folders.permissions:read": newMapping(RelationGetPermissions, ""), + "folders.permissions:write": newMapping(RelationSetPermissions, ""), + "dashboards.permissions:read": newScopedMapping(RelationGetPermissions, dashboardGroup, dashboardResource, ""), + "dashboards.permissions:write": newScopedMapping(RelationSetPermissions, dashboardGroup, dashboardResource, ""), // Action sets "folders:view": newMapping(RelationSetView, ""), "folders:edit": newMapping(RelationSetEdit, ""), @@ -96,6 +101,9 @@ var resourceTranslations = map[string]resourceTranslation{ "dashboards:write": newMapping(RelationUpdate, ""), "dashboards:create": newMapping(RelationCreate, ""), "dashboards:delete": newMapping(RelationDelete, ""), + // Permission management + "dashboards.permissions:read": newMapping(RelationGetPermissions, ""), + "dashboards.permissions:write": newMapping(RelationSetPermissions, ""), // Action sets "dashboards:view": newMapping(RelationSetView, ""), "dashboards:edit": newMapping(RelationSetEdit, ""), diff --git a/pkg/services/authz/zanzana/common/tuple_test.go b/pkg/services/authz/zanzana/common/tuple_test.go index ecb4d6e9dd1af..722694e8e8578 100644 --- a/pkg/services/authz/zanzana/common/tuple_test.go +++ b/pkg/services/authz/zanzana/common/tuple_test.go @@ -77,6 +77,84 @@ func TestTranslateToResourceTuple(t *testing.T) { Object: "group_resource:folder.grafana.app/folders", }, }, + { + testName: "folders.permissions:write for all folders", + subject: "user:1", + action: "folders.permissions:write", + kind: "folders", + name: "*", + expected: &openfgav1.TupleKey{ + User: "user:1", + Relation: "set_permissions", + Object: "group_resource:folder.grafana.app/folders", + }, + }, + { + testName: "folders.permissions:read for a specific folder", + subject: "user:1", + action: "folders.permissions:read", + kind: "folders", + name: "fold1", + expected: &openfgav1.TupleKey{ + User: "user:1", + Relation: "get_permissions", + Object: "folder:fold1", + }, + }, + { + testName: "dashboards.permissions:write for all dashboards in a folder", + subject: "user:1", + action: "dashboards.permissions:write", + kind: "folders", + name: "fold1", + expected: &openfgav1.TupleKey{ + User: "user:1", + Relation: "resource_set_permissions", + Object: "folder:fold1", + Condition: &openfgav1.RelationshipCondition{ + Name: "subresource_filter", + Context: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "subresources": structpb.NewListValue(&structpb.ListValue{ + Values: []*structpb.Value{structpb.NewStringValue("dashboard.grafana.app/dashboards")}, + }), + }, + }, + }, + }, + }, + { + testName: "dashboards.permissions:write for all dashboards in folders kind", + subject: "user:1", + action: "dashboards.permissions:write", + kind: "folders", + name: "*", + expected: &openfgav1.TupleKey{ + User: "user:1", + Relation: "set_permissions", + Object: "group_resource:dashboard.grafana.app/dashboards", + }, + }, + { + testName: "dashboards.permissions:read for a specific dashboard", + subject: "user:1", + action: "dashboards.permissions:read", + kind: "dashboards", + name: "dash1", + expected: &openfgav1.TupleKey{ + User: "user:1", + Relation: "get_permissions", + Object: "resource:dashboard.grafana.app/dashboards/dash1", + Condition: &openfgav1.RelationshipCondition{ + Name: "group_filter", + Context: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "group_resource": structpb.NewStringValue("dashboard.grafana.app/dashboards"), + }, + }, + }, + }, + }, } for _, test := range tests {
← Back to Alerts View on GitHub →