Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RBAC: Improve performance of dashboard filter query #56813

Merged
merged 25 commits into from Oct 25, 2022

Conversation

kalleep
Copy link
Contributor

@kalleep kalleep commented Oct 12, 2022

What this PR does / why we need it:
The old dashboard filter query, used in search, when using RBAC computed all permission on grafana side and provided them to the sql store as parameters.

This does not scale at all, so we need to move the RBAC check to sql.

First we check if user has a wildcard scope for both folders and dashboards, if they do we do not apply any filter. If user has no wildcard we resolve all dashboard and folder permission for a user using a sub query.

I tested this on a mysql database with 20k dashboards.

Which issue(s) this PR fixes:
Fixes https://github.com/grafana/grafana-enterprise/issues/3960

Special notes for your reviewer:

@kalleep kalleep added add to changelog area/auth/rbac Grafana role-based access control backport v9.1.x Bot will automatically open backport PR backport v9.2.x Mark PR for automatic backport to v9.2.x labels Oct 12, 2022
@kalleep kalleep added this to the 9.1.9 milestone Oct 12, 2022
@kalleep kalleep self-assigned this Oct 12, 2022
@kalleep kalleep requested review from a team as code owners October 12, 2022 15:33
@kalleep kalleep requested review from vtorosyan, IevaVasiljeva, sh0rez, yangkb09, ying-jeanne, Jguer and gamab and removed request for a team October 12, 2022 15:33
@grafanabot
Copy link
Contributor

@grafanabot
Copy link
Contributor

@grafanabot
Copy link
Contributor

@kalleep kalleep force-pushed the kalleep/rbac/dashboard-permission-search-query branch from 1c34734 to 40f90e2 Compare October 13, 2022 07:58
@grafanabot
Copy link
Contributor

@grafanabot
Copy link
Contributor

@grafanabot
Copy link
Contributor

@kalleep kalleep force-pushed the kalleep/rbac/dashboard-permission-search-query branch from 42707d2 to d04c4e8 Compare October 19, 2022 13:43
Copy link
Contributor

@IevaVasiljeva IevaVasiljeva left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I've added a few nits and a question in comments.

pkg/services/sqlstore/permissions/dashboard.go Outdated Show resolved Hide resolved
builder.WriteString(" OR dashboard.folder_id IN(SELECT id FROM dashboard WHERE ")
dashFolderFilter, _ := accesscontrol.Filter(f.User, "dashboard.uid", dashboards.ScopeFoldersPrefix, f.dashboardActions...)
if len(actionsToCheck) > 0 {
builder.WriteString("(dashboard.uid IN (SELECT substr(scope, 16) FROM permission WHERE action IN (?" + strings.Repeat(", ?", len(actionsToCheck)-1) + ") AND scope LIKE 'dashboards:uid:%' " + rolesFilter + " GROUP BY role_id, scope HAVING COUNT(scope) = ?) AND NOT dashboard.is_folder)")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: it might be worth having variables for len("dashboards:uid:") and len("folders:uid:") and use that instead of just using 16 and 13 directly 🤔

@grafanabot
Copy link
Contributor

kalleep and others added 2 commits October 19, 2022 16:03
Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
@grafanabot
Copy link
Contributor

@grafanabot
Copy link
Contributor

benchmarkDashboardPermissionFilter(b, 100, 1000)
}

func BenchmarkDashboardPermissionFilter_300_10000(b *testing.B) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you post the results from the benchmark on the PR so we have a basis we can come back to?

Copy link
Contributor

@gamab gamab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!

The only edge case I could think of is, if the actions have been granted through different roles, but that seems highly unlikely given how inconvenient it would be to create roles targeting a specific uid with a specific action.

builder.WriteString(" OR dashboard.folder_id IN(SELECT id FROM dashboard WHERE ")
dashFolderFilter, _ := accesscontrol.Filter(f.User, "dashboard.uid", dashboards.ScopeFoldersPrefix, f.dashboardActions...)
if len(actionsToCheck) > 0 {
builder.WriteString("(dashboard.uid IN (SELECT substr(scope, 16) FROM permission WHERE action IN (?" + strings.Repeat(", ?", len(actionsToCheck)-1) + ") AND scope LIKE 'dashboards:uid:%' " + rolesFilter + " GROUP BY role_id, scope HAVING COUNT(action) = ?) AND NOT dashboard.is_folder)")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't the GROUP BY role_id, scope HAVING COUNT(action) = ? assumes that the actions have been granted with the same role?
If we assume users will go through managed permissions, I guess that's ok given this is the case.

Edit: How likely is the edge case of actions been granted through different roles? Not that likely.

Comment on lines +121 to +133
actionsToCheck := make([]interface{}, 0, len(f.dashboardActions))
for _, action := range f.dashboardActions {
var hasWildcard bool
for _, scope := range f.user.Permissions[f.user.OrgID][action] {
if dashWildcards.Contains(scope) || folderWildcards.Contains(scope) {
hasWildcard = true
break
}
}
if !hasWildcard {
actionsToCheck = append(actionsToCheck, action)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would place this in a function to dedup some code and ease reading.

Copy link
Contributor

@gamab gamab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested on postgres, created a few folders (Admin only, Editors only) a few dashboards (Admin only in General, Editors only in general, and in each folders), I granted Admin to a user, Edit to a team, everything seemed to be working as expected.

@IevaVasiljeva
Copy link
Contributor

I've tested this with 100 folders, 10 000 dashboards, 100 users and 50 folder permissions and 1000 dashboard permissions per user. Everything seemed to work correctly as far as I could tell. I didn't play around too much with basic role assignments, but I think that should have a minimal impact performance wise.

Note that running the main branch at this scale also didn't lead to any noticeable performance degradation, so I'm relying on the bench marks for showing the performance improvement of these changes.

@kalleep
Copy link
Contributor Author

kalleep commented Oct 21, 2022

Note that running the main branch at this scale also didn't lead to any noticeable performance degradation, so I'm relying on the bench marks for showing the performance improvement of these changes.

@IevaVasiljeva When using sqlite the current implementation is fine. The performance issues is when using either mysql or postgres

@kalleep kalleep modified the milestones: 9.2.2, 9.3.0 Oct 24, 2022
@kalleep kalleep added no-backport Skip backport of PR and removed backport v9.2.x Mark PR for automatic backport to v9.2.x labels Oct 25, 2022
@kalleep kalleep merged commit 7386f86 into main Oct 25, 2022
@kalleep kalleep deleted the kalleep/rbac/dashboard-permission-search-query branch October 25, 2022 09:14
@leandro-deveikis leandro-deveikis modified the milestones: 9.3.0, 9.3.0-beta1 Nov 14, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants