Access Control / RBAC

MEDIUM
grafana/grafana
Commit: 14dec9e81985
Affected: < 12.4.0 (vulnerable releases prior to this fix)
2026-06-04 19:53 UTC

Description

The commit improves RBAC/datasource permission handling by mapping legacy datasource actions, including granular permissions (datasources.permissions:read and datasources.permissions:write), to their corresponding Kubernetes RBAC actions. It introduces legacyActionToK8s and expands LegacyDatasourceAction to handle both plain datasource verbs and the nested permissions verbs. This tightens authorization semantics and reduces the risk of mis-mapped permissions that could lead to unintended access (privilege escalation or information disclosure) through incorrect interpretation of datasource-related actions by the RBAC layer.

Commit Details

Author: Cory Forseth

Date: 2026-06-04 19:19 UTC

Message:

RBAC: map datasource permission read/write actions (#125802) map datasource permission read/write actions

Triage Assessment

Vulnerability Type: Access Control / RBAC correctness

Confidence: MEDIUM

Reasoning:

The commit adds mapping for datasource permission actions (read/write) in RBAC, including handling of granular permissions (datasources.permissions:read/write) and corresponding k8s actions. This tightens and corrects authorization behavior, reducing risk of mis-mapped permissions which could lead to unintended access (privilege escalation or information disclosure). Although the commit does not fix a specific disclosed vulnerability, it directly improves access control correctness in a security-sensitive area.

Verification Assessment

Vulnerability Type: Access Control / RBAC

Confidence: MEDIUM

Affected Versions: < 12.4.0 (vulnerable releases prior to this fix)

Code Diff

diff --git a/pkg/registry/apis/iam/datasourcek8s/k8s.go b/pkg/registry/apis/iam/datasourcek8s/k8s.go index 1c9c247b6ad60..ec5aaac6c8330 100644 --- a/pkg/registry/apis/iam/datasourcek8s/k8s.go +++ b/pkg/registry/apis/iam/datasourcek8s/k8s.go @@ -41,13 +41,33 @@ func LegacyVerbToK8sAction(dsType, legacyVerb string) string { } } +// legacyActionToK8s converts a legacy datasource action to its k8s form. +// It handles the resource-permissions actions (datasources.permissions:read/write) +// and the plain datasource verbs (datasources:read/write/…). Returns the converted +// action and whether a conversion was applied. +func legacyActionToK8s(dsType, action string) (string, bool) { + if permVerb, ok := strings.CutPrefix(action, "datasources.permissions:"); ok { + switch permVerb { + case "read": + return K8sDatasourceAPIGroup(dsType) + "/datasources:get_permissions", true + case "write": + return K8sDatasourceAPIGroup(dsType) + "/datasources:set_permissions", true + default: + return action, false + } + } + legacyVerb, ok := strings.CutPrefix(action, "datasources:") + if !ok || strings.Contains(legacyVerb, ":") { + return action, false + } + return LegacyVerbToK8sAction(dsType, legacyVerb), true +} + // LegacyDatasourceAction replaces a legacy ds action string with its k8s form func LegacyDatasourceAction(dsType string, action *string) { - legacyVerb, ok := strings.CutPrefix(*action, "datasources:") - if !ok || strings.Contains(legacyVerb, ":") { - return + if converted, ok := legacyActionToK8s(dsType, *action); ok { + *action = converted } - *action = LegacyVerbToK8sAction(dsType, legacyVerb) } // LegacyDatasourceScopeAndActionToK8s converts legacy datasource scope and action to k8s form @@ -58,8 +78,8 @@ func LegacyDatasourceScopeAndActionToK8s(datasourceType, scope, action string) ( if uid, ok := strings.CutPrefix(scope, "datasources:uid:"); ok { scope = LegacyUIDScopeToK8s(datasourceType, uid) } - if legacyVerb, ok := strings.CutPrefix(action, "datasources:"); ok { - action = LegacyVerbToK8sAction(datasourceType, legacyVerb) + if converted, ok := legacyActionToK8s(datasourceType, action); ok { + action = converted } return scope, action } diff --git a/pkg/registry/apis/iam/datasourcek8s/k8s_test.go b/pkg/registry/apis/iam/datasourcek8s/k8s_test.go index a8e343080403c..d6b1acb089d57 100644 --- a/pkg/registry/apis/iam/datasourcek8s/k8s_test.go +++ b/pkg/registry/apis/iam/datasourcek8s/k8s_test.go @@ -46,9 +46,19 @@ func TestLegacyDatasourceAction(t *testing.T) { LegacyDatasourceAction("loki", &a) assert.Equal(t, "loki.datasource.grafana.app/datasources:get", a) }) - t.Run("skips nested resource", func(t *testing.T) { + t.Run("maps permissions read", func(t *testing.T) { a := "datasources.permissions:read" LegacyDatasourceAction("loki", &a) - assert.Equal(t, "datasources.permissions:read", a) + assert.Equal(t, "loki.datasource.grafana.app/datasources:get_permissions", a) + }) + t.Run("maps permissions write", func(t *testing.T) { + a := "datasources.permissions:write" + LegacyDatasourceAction("*", &a) + assert.Equal(t, "*.datasource.grafana.app/datasources:set_permissions", a) + }) + t.Run("skips unknown nested resource", func(t *testing.T) { + a := "datasources.id:read" + LegacyDatasourceAction("loki", &a) + assert.Equal(t, "datasources.id:read", a) }) } diff --git a/pkg/registry/apis/iam/datasourcek8s/legacy.go b/pkg/registry/apis/iam/datasourcek8s/legacy.go index 4c969dcb4095a..d925833d1936d 100644 --- a/pkg/registry/apis/iam/datasourcek8s/legacy.go +++ b/pkg/registry/apis/iam/datasourcek8s/legacy.go @@ -30,6 +30,10 @@ func K8sDSActionToLegacy(action string) (string, bool) { return "datasources:write", true case "delete": return "datasources:delete", true + case "get_permissions": + return "datasources.permissions:read", true + case "set_permissions": + return "datasources.permissions:write", true default: return "", false } diff --git a/pkg/registry/apis/iam/datasourcek8s/legacy_test.go b/pkg/registry/apis/iam/datasourcek8s/legacy_test.go index 23aacb3672e37..158c13e4bd176 100644 --- a/pkg/registry/apis/iam/datasourcek8s/legacy_test.go +++ b/pkg/registry/apis/iam/datasourcek8s/legacy_test.go @@ -79,6 +79,18 @@ func TestK8sDSActionToLegacy(t *testing.T) { expectedAction: "datasources:read", expectedOk: true, }, + { + name: "datasource api group get_permissions verb", + action: "loki.datasource.grafana.app/datasources:get_permissions", + expectedAction: "datasources.permissions:read", + expectedOk: true, + }, + { + name: "datasource api group set_permissions verb", + action: "*.datasource.grafana.app/datasources:set_permissions", + expectedAction: "datasources.permissions:write", + expectedOk: true, + }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) {
← Back to Alerts View on GitHub →