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
RBAC: Improve performance of dashboard filter query #56813
Conversation
Drone build failed: https://drone.grafana.net/grafana/grafana-enterprise/37375 |
Drone build failed: https://drone.grafana.net/grafana/grafana-enterprise/37406 |
Drone build failed: https://drone.grafana.net/grafana/grafana-enterprise/37438 |
1c34734
to
40f90e2
Compare
Drone build failed: https://drone.grafana.net/grafana/grafana-enterprise/37448 |
Drone build failed: https://drone.grafana.net/grafana/grafana-enterprise/37486 |
Drone build failed: https://drone.grafana.net/grafana/grafana-enterprise/37489 |
42707d2
to
d04c4e8
Compare
There was a problem hiding this 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.
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)") |
There was a problem hiding this comment.
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 🤔
Drone build failed: https://drone.grafana.net/grafana/grafana-enterprise/38313 |
Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
Drone build failed: https://drone.grafana.net/grafana/grafana-enterprise/38316 |
Drone build failed: https://drone.grafana.net/grafana/grafana-enterprise/38331 |
benchmarkDashboardPermissionFilter(b, 100, 1000) | ||
} | ||
|
||
func BenchmarkDashboardPermissionFilter_300_10000(b *testing.B) { |
There was a problem hiding this comment.
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?
There was a problem hiding this 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)") |
There was a problem hiding this comment.
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.
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) | ||
} | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this 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.
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. |
@IevaVasiljeva When using sqlite the current implementation is fine. The performance issues is when using either mysql or postgres |
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: