Provisioning: allow access check to proceed even when non access policy (#112946)

* Provisioning: allow access check to proceed even when non access policy

* Provisioning: access checker needs this for MT

* add permissions registration

* remove scopes

* use in MT for now

* no need to document an internal flag here

* revert vscode change

* refactor the authZ permission evaluation and mapper code to allow evaluating unscoped actions beyond creation

* update wire

* gofmt

* add boolean to struct

---------

Co-authored-by: IevaVasiljeva <ieva.vasiljeva@grafana.com>
This commit is contained in:
Charandas
2025-11-02 13:14:08 -08:00
committed by GitHub
co-authored by IevaVasiljeva
parent 445e88cb93
commit 6c728f8dec
8 changed files with 268 additions and 65 deletions
+54 -31
View File
@@ -24,17 +24,18 @@ type Mapping interface {
AllActions() []string
// HasFolderSupport returns true if the translation supports folders.
HasFolderSupport() bool
// SkipScopeOnCreate returns true if the translation does not require a scope on create.
SkipScopeOnCreate() bool
// SkipScope returns true if the translation does not require a scope for the given verb.
SkipScope(verb string) bool
}
type translation struct {
resource string
attribute string
verbMapping map[string]string
actionSetMapping map[string][]string
folderSupport bool
skipScopeOnCreate bool
resource string
attribute string
verbMapping map[string]string
actionSetMapping map[string][]string
folderSupport bool
// actions to skip scope on, e.g., create actions
skipScopeOnVerb map[string]bool
// use this option if you need to limit access to users that can access all resources
useWildcardScope bool
}
@@ -77,8 +78,11 @@ func (t translation) HasFolderSupport() bool {
return t.folderSupport
}
func (t translation) SkipScopeOnCreate() bool {
return t.skipScopeOnCreate
func (t translation) SkipScope(verb string) bool {
if t.skipScopeOnVerb != nil {
return t.skipScopeOnVerb[verb]
}
return false
}
// MapperRegistry is a registry of mappers that maps a group and resource to a translation.
@@ -92,7 +96,7 @@ type MapperRegistry interface {
type mapper map[string]map[string]translation
func newResourceTranslation(resource string, attribute string, folderSupport, skipScopeOnCreate bool) translation {
func newResourceTranslation(resource string, attribute string, folderSupport bool, skipScopeOnVerb map[string]bool) translation {
defaultMapping := func(r string) map[string]string {
return map[string]string{
utils.VerbGet: fmt.Sprintf("%s:read", r),
@@ -109,17 +113,17 @@ func newResourceTranslation(resource string, attribute string, folderSupport, sk
}
return translation{
resource: resource,
attribute: attribute,
verbMapping: defaultMapping(resource),
folderSupport: folderSupport,
skipScopeOnCreate: skipScopeOnCreate,
resource: resource,
attribute: attribute,
verbMapping: defaultMapping(resource),
folderSupport: folderSupport,
skipScopeOnVerb: skipScopeOnVerb,
}
}
// newDashboardTranslation creates a translation for dashboards and also maps the actions to action sets
func newDashboardTranslation() translation {
dashTranslation := newResourceTranslation("dashboards", "uid", true, false)
dashTranslation := newResourceTranslation("dashboards", "uid", true, nil)
actionSetMapping := make(map[string][]string)
for verb, rbacAction := range dashTranslation.verbMapping {
@@ -152,7 +156,7 @@ func newDashboardTranslation() translation {
// newFolderTranslation creates a translation for folders and also maps the actions to action sets
func newFolderTranslation() translation {
folderTranslation := newResourceTranslation("folders", "uid", true, false)
folderTranslation := newResourceTranslation("folders", "uid", true, nil)
actionSetMapping := make(map[string][]string)
for verb, rbacAction := range folderTranslation.verbMapping {
@@ -179,21 +183,33 @@ func newFolderTranslation() translation {
}
func NewMapperRegistry() MapperRegistry {
skipScopeOnAllVerbs := map[string]bool{
utils.VerbCreate: true,
utils.VerbGet: true,
utils.VerbUpdate: true,
utils.VerbPatch: true,
utils.VerbDelete: true,
utils.VerbDeleteCollection: true,
utils.VerbList: true,
utils.VerbWatch: true,
utils.VerbGetPermissions: true,
utils.VerbSetPermissions: true,
}
mapper := mapper(map[string]map[string]translation{
"dashboard.grafana.app": {
"dashboards": newDashboardTranslation(),
"librarypanels": newResourceTranslation("library.panels", "uid", true, false),
"librarypanels": newResourceTranslation("library.panels", "uid", true, nil),
},
"folder.grafana.app": {
"folders": newFolderTranslation(),
},
"iam.grafana.app": {
// Users is a special case. We translate user permissions from id to uid based.
"users": newResourceTranslation("users", "uid", false, true),
"serviceaccounts": newResourceTranslation("serviceaccounts", "uid", false, true),
"users": newResourceTranslation("users", "uid", false, map[string]bool{utils.VerbCreate: true}),
"serviceaccounts": newResourceTranslation("serviceaccounts", "uid", false, map[string]bool{utils.VerbCreate: true}),
// Teams is a special case. We translate user permissions from id to uid based.
"teams": newResourceTranslation("teams", "uid", false, true),
// No need to skip scope on create for roles because we translate `permissions:type:delegate` to `roles:*``
"teams": newResourceTranslation("teams", "uid", false, map[string]bool{utils.VerbCreate: true}),
"coreroles": translation{
resource: "roles",
attribute: "uid",
@@ -202,8 +218,9 @@ func NewMapperRegistry() MapperRegistry {
utils.VerbList: "roles:read",
utils.VerbWatch: "roles:read",
},
folderSupport: false,
skipScopeOnCreate: false,
folderSupport: false,
// No need to skip scope on create for roles because we translate `permissions:type:delegate` to `roles:*``
skipScopeOnVerb: nil,
},
"roles": translation{
resource: "roles",
@@ -218,8 +235,9 @@ func NewMapperRegistry() MapperRegistry {
utils.VerbList: "roles:read",
utils.VerbWatch: "roles:read",
},
folderSupport: false,
skipScopeOnCreate: false,
folderSupport: false,
// No need to skip scope on create for roles because we translate `permissions:type:delegate` to `roles:*``
skipScopeOnVerb: nil,
},
"rolebindings": translation{
resource: "rolebindings",
@@ -238,9 +256,14 @@ func NewMapperRegistry() MapperRegistry {
folderSupport: false,
},
},
"provisioning.grafana.app": {
"repositories": newResourceTranslation("provisioning.repositories", "uid", false, skipScopeOnAllVerbs),
"jobs": newResourceTranslation("provisioning.jobs", "uid", false, skipScopeOnAllVerbs),
"historicjobs": newResourceTranslation("provisioning.historicjobs", "uid", false, skipScopeOnAllVerbs),
},
"secret.grafana.app": {
"securevalues": newResourceTranslation("secret.securevalues", "uid", false, false),
"keepers": newResourceTranslation("secret.keepers", "uid", false, false),
"securevalues": newResourceTranslation("secret.securevalues", "uid", false, nil),
"keepers": newResourceTranslation("secret.keepers", "uid", false, nil),
},
"query.grafana.app": {
"query": translation{
@@ -249,8 +272,8 @@ func NewMapperRegistry() MapperRegistry {
verbMapping: map[string]string{
utils.VerbCreate: "datasources:query",
},
folderSupport: false,
skipScopeOnCreate: false,
folderSupport: false,
skipScopeOnVerb: nil,
},
},
})
+20 -10
View File
@@ -617,18 +617,28 @@ func (s *Service) checkPermission(ctx context.Context, scopeMap map[string]bool,
return false, status.Error(codes.NotFound, "unsupported resource")
}
if req.Verb == utils.VerbCreate {
// Resource doesn't require scope on create, so allow if the user has the action
if t.SkipScopeOnCreate() {
return scopeMap[""], nil
}
// If creating a resource that goes in a folder, but no folder is specified,
// assume parent folder is the general folder
if t.HasFolderSupport() && req.ParentFolder == "" {
req.ParentFolder = accesscontrol.GeneralFolderUID
}
if t.SkipScope(req.Verb) {
return scopeMap[""], nil
}
// If creating a resource that goes in a folder, but no folder is specified,
// assume parent folder is the general folder
if req.Verb == utils.VerbCreate && t.HasFolderSupport() && req.ParentFolder == "" {
req.ParentFolder = accesscontrol.GeneralFolderUID
}
//if req.Verb == utils.VerbCreate {
// // Resource doesn't require scope on create, so allow if the user has the action
// if t.SkipScopeOnCreate() {
// return scopeMap[""], nil
// }
// // If creating a resource that goes in a folder, but no folder is specified,
// // assume parent folder is the general folder
// if t.HasFolderSupport() && req.ParentFolder == "" {
// req.ParentFolder = accesscontrol.GeneralFolderUID
// }
//}
// Wildcard grant, no further checks needed
if scopeMap["*"] {
return true, nil