Access control: Move data source actions and scopes to datasource package (#46594)

* Add permission actions and id scope

* Remove scope and actions variable prefix

* Move page evaluators and rename them
pull/46646/head
Karl Persson 3 years ago committed by GitHub
parent ea815d640f
commit d27ff42376
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 49
      pkg/api/accesscontrol.go
  2. 42
      pkg/api/api.go
  3. 26
      pkg/api/datasources_test.go
  4. 3
      pkg/api/index.go
  5. 46
      pkg/services/datasources/accesscontrol.go
  6. 8
      pkg/services/datasources/service/datasource_service.go

@ -83,12 +83,12 @@ func (hs *HTTPServer) declareFixedRoles() error {
Group: "Data sources",
Permissions: []ac.Permission{
{
Action: datasources.ActionDatasourcesRead,
Scope: datasources.ScopeDatasourcesProvider.GetResourceAllScope(),
Action: datasources.ActionRead,
Scope: datasources.ScopeAll,
},
{
Action: datasources.ActionDatasourcesQuery,
Scope: datasources.ScopeDatasourcesAll,
Action: datasources.ActionQuery,
Scope: datasources.ScopeAll,
},
},
},
@ -104,15 +104,15 @@ func (hs *HTTPServer) declareFixedRoles() error {
Group: "Data sources",
Permissions: ac.ConcatPermissions(datasourcesReaderRole.Role.Permissions, []ac.Permission{
{
Action: datasources.ActionDatasourcesWrite,
Scope: datasources.ScopeDatasourcesAll,
Action: datasources.ActionWrite,
Scope: datasources.ScopeAll,
},
{
Action: datasources.ActionDatasourcesCreate,
Action: datasources.ActionCreate,
},
{
Action: datasources.ActionDatasourcesDelete,
Scope: datasources.ScopeDatasourcesAll,
Action: datasources.ActionDelete,
Scope: datasources.ScopeAll,
},
}),
},
@ -128,8 +128,8 @@ func (hs *HTTPServer) declareFixedRoles() error {
Group: "Infrequently used",
Permissions: []ac.Permission{
{
Action: datasources.ActionDatasourcesIDRead,
Scope: datasources.ScopeDatasourcesAll,
Action: datasources.ActionIDRead,
Scope: datasources.ScopeAll,
},
},
},
@ -144,8 +144,8 @@ func (hs *HTTPServer) declareFixedRoles() error {
Description: "Only used for open source compatibility. Query data sources.",
Group: "Infrequently used",
Permissions: []ac.Permission{
{Action: datasources.ActionDatasourcesQuery},
{Action: datasources.ActionDatasourcesRead},
{Action: datasources.ActionQuery},
{Action: datasources.ActionRead},
},
},
Grants: []string{string(models.ROLE_VIEWER)},
@ -387,29 +387,6 @@ func (hs *HTTPServer) declareFixedRoles() error {
// Evaluators
// here is the list of complex evaluators we use in this package
// dataSourcesConfigurationAccessEvaluator is used to protect the "Configure > Data sources" tab access
var dataSourcesConfigurationAccessEvaluator = ac.EvalAll(
ac.EvalPermission(datasources.ActionDatasourcesRead),
ac.EvalAny(
ac.EvalPermission(datasources.ActionDatasourcesCreate),
ac.EvalPermission(datasources.ActionDatasourcesDelete),
ac.EvalPermission(datasources.ActionDatasourcesWrite),
),
)
// dataSourcesNewAccessEvaluator is used to protect the "Configure > Data sources > New" page access
var dataSourcesNewAccessEvaluator = ac.EvalAll(
ac.EvalPermission(datasources.ActionDatasourcesRead),
ac.EvalPermission(datasources.ActionDatasourcesCreate),
ac.EvalPermission(datasources.ActionDatasourcesWrite),
)
// dataSourcesEditAccessEvaluator is used to protect the "Configure > Data sources > Edit" page access
var dataSourcesEditAccessEvaluator = ac.EvalAll(
ac.EvalPermission(datasources.ActionDatasourcesRead),
ac.EvalPermission(datasources.ActionDatasourcesWrite),
)
// orgPreferencesAccessEvaluator is used to protect the "Configure > Preferences" page access
var orgPreferencesAccessEvaluator = ac.EvalAny(
ac.EvalAll(

@ -53,9 +53,9 @@ func (hs *HTTPServer) registerRoutes() {
r.Get("/profile/switch-org/:id", reqSignedInNoAnonymous, hs.ChangeActiveOrgAndRedirectToHome)
r.Get("/org/", authorize(reqOrgAdmin, orgPreferencesAccessEvaluator), hs.Index)
r.Get("/org/new", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseGlobalOrg, orgsCreateAccessEvaluator), hs.Index)
r.Get("/datasources/", authorize(reqOrgAdmin, dataSourcesConfigurationAccessEvaluator), hs.Index)
r.Get("/datasources/new", authorize(reqOrgAdmin, dataSourcesNewAccessEvaluator), hs.Index)
r.Get("/datasources/edit/*", authorize(reqOrgAdmin, dataSourcesEditAccessEvaluator), hs.Index)
r.Get("/datasources/", authorize(reqOrgAdmin, datasources.ConfigurationPageAccess), hs.Index)
r.Get("/datasources/new", authorize(reqOrgAdmin, datasources.NewPageAccess), hs.Index)
r.Get("/datasources/edit/*", authorize(reqOrgAdmin, datasources.EditPageAccess), hs.Index)
r.Get("/org/users", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead)), hs.Index)
r.Get("/org/users/new", reqOrgAdmin, hs.Index)
r.Get("/org/users/invite", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionUsersCreate)), hs.Index)
@ -275,18 +275,18 @@ func (hs *HTTPServer) registerRoutes() {
// Data sources
apiRoute.Group("/datasources", func(datasourceRoute routing.RouteRegister) {
datasourceRoute.Get("/", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionDatasourcesRead)), routing.Wrap(hs.GetDataSources))
datasourceRoute.Post("/", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionDatasourcesCreate)), quota("data_source"), routing.Wrap(hs.AddDataSource))
datasourceRoute.Put("/:id", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionDatasourcesWrite, datasources.ScopeDatasourcesProvider.GetResourceScope(ac.Parameter(":id")))), routing.Wrap(hs.UpdateDataSource))
datasourceRoute.Delete("/:id", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionDatasourcesDelete, datasources.ScopeDatasourcesProvider.GetResourceScope(ac.Parameter(":id")))), routing.Wrap(hs.DeleteDataSourceById))
datasourceRoute.Delete("/uid/:uid", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionDatasourcesDelete, datasources.ScopeDatasourcesProvider.GetResourceScopeUID(ac.Parameter(":uid")))), routing.Wrap(hs.DeleteDataSourceByUID))
datasourceRoute.Delete("/name/:name", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionDatasourcesDelete, datasources.ScopeDatasourcesProvider.GetResourceScopeName(ac.Parameter(":name")))), routing.Wrap(hs.DeleteDataSourceByName))
datasourceRoute.Get("/:id", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionDatasourcesRead)), routing.Wrap(hs.GetDataSourceById))
datasourceRoute.Get("/uid/:uid", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionDatasourcesRead)), routing.Wrap(hs.GetDataSourceByUID))
datasourceRoute.Get("/name/:name", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionDatasourcesRead)), routing.Wrap(hs.GetDataSourceByName))
datasourceRoute.Get("/", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionRead)), routing.Wrap(hs.GetDataSources))
datasourceRoute.Post("/", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionCreate)), quota("data_source"), routing.Wrap(hs.AddDataSource))
datasourceRoute.Put("/:id", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":id")))), routing.Wrap(hs.UpdateDataSource))
datasourceRoute.Delete("/:id", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionDelete, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":id")))), routing.Wrap(hs.DeleteDataSourceById))
datasourceRoute.Delete("/uid/:uid", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionDelete, datasources.ScopeProvider.GetResourceScopeUID(ac.Parameter(":uid")))), routing.Wrap(hs.DeleteDataSourceByUID))
datasourceRoute.Delete("/name/:name", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionDelete, datasources.ScopeProvider.GetResourceScopeName(ac.Parameter(":name")))), routing.Wrap(hs.DeleteDataSourceByName))
datasourceRoute.Get("/:id", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionRead)), routing.Wrap(hs.GetDataSourceById))
datasourceRoute.Get("/uid/:uid", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionRead)), routing.Wrap(hs.GetDataSourceByUID))
datasourceRoute.Get("/name/:name", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionRead)), routing.Wrap(hs.GetDataSourceByName))
})
apiRoute.Get("/datasources/id/:name", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionDatasourcesIDRead, datasources.ScopeDatasourcesProvider.GetResourceScopeName(ac.Parameter(":name")))), routing.Wrap(hs.GetDataSourceIdByName))
apiRoute.Get("/datasources/id/:name", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionIDRead, datasources.ScopeProvider.GetResourceScopeName(ac.Parameter(":name")))), routing.Wrap(hs.GetDataSourceIdByName))
apiRoute.Get("/plugins", routing.Wrap(hs.GetPluginList))
apiRoute.Get("/plugins/:pluginId/settings", routing.Wrap(hs.GetPluginSettingByID))
@ -308,11 +308,11 @@ func (hs *HTTPServer) registerRoutes() {
}, reqOrgAdmin)
apiRoute.Get("/frontend/settings/", hs.GetFrontendSettings)
apiRoute.Any("/datasources/proxy/:id/*", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionDatasourcesQuery)), hs.ProxyDataSourceRequest)
apiRoute.Any("/datasources/proxy/:id", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionDatasourcesQuery)), hs.ProxyDataSourceRequest)
apiRoute.Any("/datasources/:id/resources", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionDatasourcesQuery)), hs.CallDatasourceResource)
apiRoute.Any("/datasources/:id/resources/*", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionDatasourcesQuery)), hs.CallDatasourceResource)
apiRoute.Any("/datasources/:id/health", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionDatasourcesQuery)), routing.Wrap(hs.CheckDatasourceHealth))
apiRoute.Any("/datasources/proxy/:id/*", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionQuery)), hs.ProxyDataSourceRequest)
apiRoute.Any("/datasources/proxy/:id", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionQuery)), hs.ProxyDataSourceRequest)
apiRoute.Any("/datasources/:id/resources", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionQuery)), hs.CallDatasourceResource)
apiRoute.Any("/datasources/:id/resources/*", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionQuery)), hs.CallDatasourceResource)
apiRoute.Any("/datasources/:id/health", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionQuery)), routing.Wrap(hs.CheckDatasourceHealth))
// Folders
apiRoute.Group("/folders", func(folderRoute routing.RouteRegister) {
@ -386,13 +386,13 @@ func (hs *HTTPServer) registerRoutes() {
apiRoute.Get("/search/", routing.Wrap(hs.Search))
// metrics
apiRoute.Post("/tsdb/query", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionDatasourcesQuery)), routing.Wrap(hs.QueryMetrics))
apiRoute.Post("/tsdb/query", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionQuery)), routing.Wrap(hs.QueryMetrics))
// DataSource w/ expressions
apiRoute.Post("/ds/query", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionDatasourcesQuery)), routing.Wrap(hs.QueryMetricsV2))
apiRoute.Post("/ds/query", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionQuery)), routing.Wrap(hs.QueryMetricsV2))
// Validated query
apiRoute.Post("/dashboards/org/:orgId/uid/:dashboardUid/panels/:panelId/query", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionDatasourcesQuery)), routing.Wrap(hs.QueryMetricsFromDashboard))
apiRoute.Post("/dashboards/org/:orgId/uid/:dashboardUid/panels/:panelId/query", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionQuery)), routing.Wrap(hs.QueryMetricsFromDashboard))
apiRoute.Group("/alerts", func(alertsRoute routing.RouteRegister) {
alertsRoute.Post("/test", routing.Wrap(hs.AlertTest))

@ -241,8 +241,8 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
method: http.MethodPut,
permissions: []*ac.Permission{
{
Action: datasources.ActionDatasourcesWrite,
Scope: datasources.ScopeDatasourcesAll,
Action: datasources.ActionWrite,
Scope: datasources.ScopeAll,
},
},
},
@ -254,7 +254,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
desc: "DatasourcesGet should return 200 for user with correct permissions",
url: "/api/datasources/",
method: http.MethodGet,
permissions: []*ac.Permission{{Action: datasources.ActionDatasourcesRead, Scope: datasources.ScopeDatasourcesAll}},
permissions: []*ac.Permission{{Action: datasources.ActionRead, Scope: datasources.ScopeAll}},
},
},
{
@ -273,7 +273,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
desc: "DatasourcesPost should return 200 for user with correct permissions",
url: "/api/datasources/",
method: http.MethodPost,
permissions: []*ac.Permission{{Action: datasources.ActionDatasourcesCreate}},
permissions: []*ac.Permission{{Action: datasources.ActionCreate}},
},
expectedDS: &testDatasource,
},
@ -295,7 +295,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
method: http.MethodPut,
permissions: []*ac.Permission{
{
Action: datasources.ActionDatasourcesWrite,
Action: datasources.ActionWrite,
Scope: fmt.Sprintf("datasources:id:%v", testDatasource.Id),
},
},
@ -320,7 +320,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
method: http.MethodPut,
permissions: []*ac.Permission{
{
Action: datasources.ActionDatasourcesWrite,
Action: datasources.ActionWrite,
Scope: fmt.Sprintf("datasources:id:%v", testDatasourceReadOnly.Id),
},
},
@ -335,7 +335,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
method: http.MethodDelete,
permissions: []*ac.Permission{
{
Action: datasources.ActionDatasourcesDelete,
Action: datasources.ActionDelete,
Scope: fmt.Sprintf("datasources:id:%v", testDatasource.Id),
},
},
@ -359,7 +359,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
method: http.MethodDelete,
permissions: []*ac.Permission{
{
Action: datasources.ActionDatasourcesDelete,
Action: datasources.ActionDelete,
Scope: fmt.Sprintf("datasources:uid:%v", testDatasource.Uid),
},
},
@ -383,7 +383,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
method: http.MethodDelete,
permissions: []*ac.Permission{
{
Action: datasources.ActionDatasourcesDelete,
Action: datasources.ActionDelete,
Scope: fmt.Sprintf("datasources:name:%v", testDatasource.Name),
},
},
@ -407,7 +407,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
method: http.MethodGet,
permissions: []*ac.Permission{
{
Action: datasources.ActionDatasourcesRead,
Action: datasources.ActionRead,
Scope: fmt.Sprintf("datasources:id:%v", testDatasource.Id),
},
},
@ -431,7 +431,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
method: http.MethodGet,
permissions: []*ac.Permission{
{
Action: datasources.ActionDatasourcesRead,
Action: datasources.ActionRead,
Scope: fmt.Sprintf("datasources:uid:%v", testDatasource.Uid),
},
},
@ -455,7 +455,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
method: http.MethodGet,
permissions: []*ac.Permission{
{
Action: datasources.ActionDatasourcesRead,
Action: datasources.ActionRead,
Scope: fmt.Sprintf("datasources:name:%v", testDatasource.Name),
},
},
@ -480,7 +480,7 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
method: http.MethodGet,
permissions: []*ac.Permission{
{
Action: datasources.ActionDatasourcesIDRead,
Action: datasources.ActionIDRead,
Scope: fmt.Sprintf("datasources:name:%v", testDatasource.Name),
},
},

@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/plugins"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
)
@ -244,7 +245,7 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool) ([]*dto
configNodes := []*dtos.NavLink{}
if hasAccess(ac.ReqOrgAdmin, dataSourcesConfigurationAccessEvaluator) {
if hasAccess(ac.ReqOrgAdmin, datasources.ConfigurationPageAccess) {
configNodes = append(configNodes, &dtos.NavLink{
Text: "Data sources",
Icon: "database",

@ -3,17 +3,45 @@ package datasources
import "github.com/grafana/grafana/pkg/services/accesscontrol"
const (
ActionDatasourcesRead = "datasources:read"
ActionDatasourcesQuery = "datasources:query"
ActionDatasourcesCreate = "datasources:create"
ActionDatasourcesWrite = "datasources:write"
ActionDatasourcesDelete = "datasources:delete"
ActionDatasourcesIDRead = "datasources.id:read"
ScopeRoot = "datasources"
ScopeDatasourcesRoot = "datasources"
ActionRead = "datasources:read"
ActionQuery = "datasources:query"
ActionCreate = "datasources:create"
ActionWrite = "datasources:write"
ActionDelete = "datasources:delete"
ActionIDRead = "datasources.id:read"
ActionPermissionsRead = "datasources.permissions:read"
ActionPermissionsWrite = "datasources.permissions:write"
)
var (
ScopeDatasourcesAll = accesscontrol.GetResourceAllScope(ScopeDatasourcesRoot)
ScopeDatasourcesProvider = accesscontrol.NewScopeProvider(ScopeDatasourcesRoot)
ScopeID = accesscontrol.Scope("datasources", "id", accesscontrol.Parameter(":datasourceId"))
ScopeAll = accesscontrol.GetResourceAllScope(ScopeRoot)
ScopeProvider = accesscontrol.NewScopeProvider(ScopeRoot)
)
var (
// ConfigurationPageAccess is used to protect the "Configure > Data sources" tab access
ConfigurationPageAccess = accesscontrol.EvalAll(
accesscontrol.EvalPermission(ActionRead),
accesscontrol.EvalAny(
accesscontrol.EvalPermission(ActionCreate),
accesscontrol.EvalPermission(ActionDelete),
accesscontrol.EvalPermission(ActionWrite),
),
)
// NewPageAccess is used to protect the "Configure > Data sources > New" page access
NewPageAccess = accesscontrol.EvalAll(
accesscontrol.EvalPermission(ActionRead),
accesscontrol.EvalPermission(ActionCreate),
accesscontrol.EvalPermission(ActionWrite),
)
// EditPageAccess is used to protect the "Configure > Data sources > Edit" page access
EditPageAccess = accesscontrol.EvalAll(
accesscontrol.EvalPermission(ActionRead),
accesscontrol.EvalPermission(ActionWrite),
)
)

@ -97,7 +97,7 @@ type DataSourceRetriever interface {
// NewNameScopeResolver provides an AttributeScopeResolver able to
// translate a scope prefixed with "datasources:name:" into an id based scope.
func NewNameScopeResolver(db DataSourceRetriever) (string, accesscontrol.AttributeScopeResolveFunc) {
prefix := datasources.ScopeDatasourcesProvider.GetResourceScopeName("")
prefix := datasources.ScopeProvider.GetResourceScopeName("")
dsNameResolver := func(ctx context.Context, orgID int64, initialScope string) (string, error) {
if !strings.HasPrefix(initialScope, prefix) {
return "", accesscontrol.ErrInvalidScope
@ -113,7 +113,7 @@ func NewNameScopeResolver(db DataSourceRetriever) (string, accesscontrol.Attribu
return "", err
}
return datasources.ScopeDatasourcesProvider.GetResourceScope(strconv.FormatInt(query.Result.Id, 10)), nil
return datasources.ScopeProvider.GetResourceScope(strconv.FormatInt(query.Result.Id, 10)), nil
}
return prefix, dsNameResolver
@ -122,7 +122,7 @@ func NewNameScopeResolver(db DataSourceRetriever) (string, accesscontrol.Attribu
// NewUidScopeResolver provides an AttributeScopeResolver able to
// translate a scope prefixed with "datasources:uid:" into an id based scope.
func NewUidScopeResolver(db DataSourceRetriever) (string, accesscontrol.AttributeScopeResolveFunc) {
prefix := datasources.ScopeDatasourcesProvider.GetResourceScopeUID("")
prefix := datasources.ScopeProvider.GetResourceScopeUID("")
dsUIDResolver := func(ctx context.Context, orgID int64, initialScope string) (string, error) {
if !strings.HasPrefix(initialScope, prefix) {
return "", accesscontrol.ErrInvalidScope
@ -138,7 +138,7 @@ func NewUidScopeResolver(db DataSourceRetriever) (string, accesscontrol.Attribut
return "", err
}
return datasources.ScopeDatasourcesProvider.GetResourceScope(strconv.FormatInt(query.Result.Id, 10)), nil
return datasources.ScopeProvider.GetResourceScope(strconv.FormatInt(query.Result.Id, 10)), nil
}
return prefix, dsUIDResolver

Loading…
Cancel
Save