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

Alerting: Update rule access control to return errutil errors #78284

Merged
merged 11 commits into from
Dec 1, 2023

Conversation

yuri-tceretian
Copy link
Contributor

What is this feature?
This PR updates the rule access control service to return errutil.Error errors that are used throughout the Grafana code.
Notable changes:

  • The methods AuthorizeDatasourceAccessForRule and AuthorizeAccessToRuleGroup now collect all data source scopes and evaluate permissions once.
  • The method AuthorizeRuleChanges changed to evaluate permissions on its own using a new method HasAccessOrError. This opens the possibility for better messaging.
  • All authorization methods are updated to return a new error that includes a list of permissions required to access the resource.
  • API logic changed to treat errutil.Error specially.

Now the authorization error response will look like

{
  "statusCode": 401,
  "messageId": "ngalert.unauthorized",
  "message": "user is not authorized to access rule group NO-ACCESS in folder a1309456-f4a8-4a20-8eab-b2dcdf5ef115",
  "extra": {
    "permissions": "all(action:datasources:query scopes:datasources:uid:b4aac448-430c-4c7d-a9e3-5724d1deb0a9 action:datasources:query scopes:datasources:uid:aa09159d-7e2f-4d20-a043-7e87f8634f3d)"
  }
}

Why do we need this feature?

  • This gets us closer to Grafana API conventions
  • This will let users see what permissions are missing
  • Refactoring of AuthorizeRuleChanges will let me add checks for more permissions to AuthorizeAccessToRuleGroup

Special notes for your reviewer:

Please check that:

  • It works as expected from a user's perspective.
  • If this is a pre-GA feature, it is behind a feature toggle.
  • The docs are updated, and if this is a notable improvement, it's added to our What's New doc.

@yuri-tceretian yuri-tceretian requested review from a team, rwwiv, JacobsonMT and grobinson-grafana and removed request for a team November 16, 2023 18:22
@yuri-tceretian yuri-tceretian added add to changelog no-backport Skip backport of PR no-changelog Skip including change in changelog/release notes labels Nov 16, 2023
@yuri-tceretian yuri-tceretian self-assigned this Nov 16, 2023
@grafana-delivery-bot grafana-delivery-bot bot added this to the 10.3.x milestone Nov 16, 2023
return false
// getRulesReadEvaluator constructs accesscontrol.Evaluator that checks all permission required to read all provided rules
func (r *RuleService) getRulesReadEvaluator(rules ...*models.AlertRule) accesscontrol.Evaluator {
return r.getRulesQueryEvaluator(rules...)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will add more in a follow-up PR.

@yuri-tceretian yuri-tceretian added the area/alerting Grafana Alerting label Nov 16, 2023
Copy link
Member

@JacobsonMT JacobsonMT 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 so far, going to do some manual testing but I'll post my code comments for now.

})
}

// HasAccessToRuleGroup returns false if
Copy link
Member

Choose a reason for hiding this comment

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

Missing some text in the comment

if !srv.authz.AuthorizeAccessToRuleGroup(c.Req.Context(), c.SignedInUser, ngmodels.RulesGroup{rule}) {
return errorToResponse(fmt.Errorf("%w to query one or many data sources used by the rule", accesscontrol.ErrAuthorization))
if err := srv.authz.AuthorizeAccessToRuleGroup(c.Req.Context(), c.SignedInUser, ngmodels.RulesGroup{rule}); err != nil {
return response.ErrOrFallback(http.StatusInternalServerError, "failed to authorize access to rule group", err)
Copy link
Member

Choose a reason for hiding this comment

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

NIT: Should this be errorToResponse(err) as well to be inline with the rest of the api?

Copy link
Contributor Author

@yuri-tceretian yuri-tceretian Nov 28, 2023

Choose a reason for hiding this comment

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

oh, I think it is better to do the opposite because ErrOrFallback lets us return some meaningful message in the response if error does not happen to be errutils' one.
This could be useful if authz happen to return some generic errors (eg from db or other service).

WDYT?

@@ -2284,7 +2284,7 @@ func TestIntegrationEval(t *testing.T) {
},
expectedMessage: func() string {
if setting.IsEnterprise {
return "user is not authorized to query one or many data sources used by the rule"
return "user is not authorized to access rule group in folder "
Copy link
Member

Choose a reason for hiding this comment

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

Let's quote the rule group name and folder to avoid this odd double space in

return fmt.Sprintf("access rule group %s in folder %s", groupName, folderUID)

evals := make([]accesscontrol.Evaluator, 0, 2)
for _, rule := range rules {
for _, query := range rule.Data {
if query.QueryType == expr.DatasourceType || query.DatasourceUID == expr.DatasourceUID || query.
Copy link
Member

Choose a reason for hiding this comment

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

I know this wasn't technically changed in this PR, but could consider using query.IsExpression() here. Could even do a very small refactor to remove the unused error return so it can be inlined.

Copy link
Contributor Author

@yuri-tceretian yuri-tceretian Nov 28, 2023

Choose a reason for hiding this comment

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

I would rather get rid of the query.IsExpression because it makes a dependency on expr package, and keep the logic that binds expr and ng.models packages in 3rd package.

This check is not necessary, though, and can be replaced by similar logic as

switch nodeType := expr.NodeTypeFromDatasourceUID(q.DatasourceUID); nodeType {
case expr.TypeDatasourceNode:
ds, err = dsCacheService.GetDatasourceByUID(ctx.Ctx, q.DatasourceUID, ctx.User, false /*skipCache*/)
default:
ds, err = expr.DataSourceModelFromNodeType(nodeType)
}
if err != nil {
return nil, fmt.Errorf("failed to build query '%s': %w", q.RefID, err)
}
datasources[q.DatasourceUID] = ds

to facilitate checks for MLNode in the future.

I would like to keep it as is to reduce the diff, and address in a follow-up

Copy link
Member

@JacobsonMT JacobsonMT left a comment

Choose a reason for hiding this comment

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

LGTM 🚀

@yuri-tceretian yuri-tceretian enabled auto-merge (squash) December 1, 2023 23:23
@yuri-tceretian yuri-tceretian merged commit 64feedd into main Dec 1, 2023
14 checks passed
@yuri-tceretian yuri-tceretian deleted the yuri-tceretian/fgac-rules-errutil branch December 1, 2023 23:42
@aangelisc aangelisc modified the milestones: 10.3.x, 10.2.3 Dec 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
add to changelog area/alerting Grafana Alerting area/backend no-backport Skip backport of PR no-changelog Skip including change in changelog/release notes
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

None yet

3 participants