mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Storage: use static access rules (#52334)
* Storage: use static access rules * Storage: use static access rules * Storage: add tests
This commit is contained in:
@@ -3,7 +3,6 @@ package store
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
@@ -19,13 +18,16 @@ import (
|
||||
|
||||
var grafanaStorageLogger = log.New("grafanaStorageLogger")
|
||||
|
||||
var ErrUploadFeatureDisabled = errors.New("upload feature is disabled")
|
||||
var ErrUnsupportedStorage = errors.New("storage does not support this operation")
|
||||
var ErrUploadInternalError = errors.New("upload internal error")
|
||||
var ErrValidationFailed = errors.New("request validation failed")
|
||||
var ErrFileAlreadyExists = errors.New("file exists")
|
||||
var ErrStorageNotFound = errors.New("storage not found")
|
||||
var ErrAccessDenied = errors.New("access denied")
|
||||
|
||||
const RootPublicStatic = "public-static"
|
||||
const RootResources = "resources"
|
||||
const RootDevenv = "devenv"
|
||||
|
||||
const MAX_UPLOAD_SIZE = 1 * 1024 * 1024 // 3MB
|
||||
|
||||
@@ -66,9 +68,10 @@ type storageServiceConfig struct {
|
||||
}
|
||||
|
||||
type standardStorageService struct {
|
||||
sql *sqlstore.SQLStore
|
||||
tree *nestedTree
|
||||
cfg storageServiceConfig
|
||||
sql *sqlstore.SQLStore
|
||||
tree *nestedTree
|
||||
cfg storageServiceConfig
|
||||
authService storageAuthService
|
||||
}
|
||||
|
||||
func ProvideService(sql *sqlstore.SQLStore, features featuremgmt.FeatureToggles, cfg *setting.Cfg) StorageService {
|
||||
@@ -90,7 +93,7 @@ func ProvideService(sql *sqlstore.SQLStore, features featuremgmt.FeatureToggles,
|
||||
devenv := filepath.Join(cfg.StaticRootPath, "..", "devenv")
|
||||
if _, err := os.Stat(devenv); !os.IsNotExist(err) {
|
||||
// path/to/whatever exists
|
||||
s := newDiskStorage("devenv", "Development Environment", &StorageLocalDiskConfig{
|
||||
s := newDiskStorage(RootDevenv, "Development Environment", &StorageLocalDiskConfig{
|
||||
Path: devenv,
|
||||
Roots: []string{
|
||||
"/dev-dashboards/",
|
||||
@@ -104,7 +107,7 @@ func ProvideService(sql *sqlstore.SQLStore, features featuremgmt.FeatureToggles,
|
||||
storages := make([]storageRuntime, 0)
|
||||
if features.IsEnabled(featuremgmt.FlagStorageLocalUpload) {
|
||||
storages = append(storages,
|
||||
newSQLStorage("resources",
|
||||
newSQLStorage(RootResources,
|
||||
"Resources",
|
||||
&StorageSQLConfig{orgId: orgId}, sql).
|
||||
setBuiltin(true).
|
||||
@@ -114,10 +117,39 @@ func ProvideService(sql *sqlstore.SQLStore, features featuremgmt.FeatureToggles,
|
||||
return storages
|
||||
}
|
||||
|
||||
return newStandardStorageService(sql, globalRoots, initializeOrgStorages)
|
||||
authService := newStaticStorageAuthService(func(ctx context.Context, user *models.SignedInUser, storageName string) map[string]filestorage.PathFilter {
|
||||
if user == nil || !user.IsGrafanaAdmin {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch storageName {
|
||||
case RootPublicStatic:
|
||||
return map[string]filestorage.PathFilter{
|
||||
ActionFilesRead: allowAllPathFilter,
|
||||
ActionFilesWrite: denyAllPathFilter,
|
||||
ActionFilesDelete: denyAllPathFilter,
|
||||
}
|
||||
case RootDevenv:
|
||||
return map[string]filestorage.PathFilter{
|
||||
ActionFilesRead: allowAllPathFilter,
|
||||
ActionFilesWrite: denyAllPathFilter,
|
||||
ActionFilesDelete: denyAllPathFilter,
|
||||
}
|
||||
case RootResources:
|
||||
return map[string]filestorage.PathFilter{
|
||||
ActionFilesRead: allowAllPathFilter,
|
||||
ActionFilesWrite: allowAllPathFilter,
|
||||
ActionFilesDelete: allowAllPathFilter,
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
})
|
||||
|
||||
return newStandardStorageService(sql, globalRoots, initializeOrgStorages, authService)
|
||||
}
|
||||
|
||||
func newStandardStorageService(sql *sqlstore.SQLStore, globalRoots []storageRuntime, initializeOrgStorages func(orgId int64) []storageRuntime) *standardStorageService {
|
||||
func newStandardStorageService(sql *sqlstore.SQLStore, globalRoots []storageRuntime, initializeOrgStorages func(orgId int64) []storageRuntime, authService storageAuthService) *standardStorageService {
|
||||
rootsByOrgId := make(map[int64][]storageRuntime)
|
||||
rootsByOrgId[ac.GlobalOrgID] = globalRoots
|
||||
|
||||
@@ -127,8 +159,9 @@ func newStandardStorageService(sql *sqlstore.SQLStore, globalRoots []storageRunt
|
||||
}
|
||||
res.init()
|
||||
return &standardStorageService{
|
||||
sql: sql,
|
||||
tree: res,
|
||||
sql: sql,
|
||||
tree: res,
|
||||
authService: authService,
|
||||
cfg: storageServiceConfig{
|
||||
allowUnsanitizedSvgUpload: false,
|
||||
},
|
||||
@@ -149,12 +182,15 @@ func getOrgId(user *models.SignedInUser) int64 {
|
||||
}
|
||||
|
||||
func (s *standardStorageService) List(ctx context.Context, user *models.SignedInUser, path string) (*StorageListFrame, error) {
|
||||
// apply access control here
|
||||
return s.tree.ListFolder(ctx, getOrgId(user), path)
|
||||
guardian := s.authService.newGuardian(ctx, user, getFirstSegment(path))
|
||||
return s.tree.ListFolder(ctx, getOrgId(user), path, guardian.getPathFilter(ActionFilesRead))
|
||||
}
|
||||
|
||||
func (s *standardStorageService) Read(ctx context.Context, user *models.SignedInUser, path string) (*filestorage.File, error) {
|
||||
// TODO: permission check!
|
||||
guardian := s.authService.newGuardian(ctx, user, getFirstSegment(path))
|
||||
if !guardian.canView(path) {
|
||||
return nil, ErrAccessDenied
|
||||
}
|
||||
return s.tree.GetFile(ctx, getOrgId(user), path)
|
||||
}
|
||||
|
||||
@@ -171,12 +207,17 @@ type UploadRequest struct {
|
||||
}
|
||||
|
||||
func (s *standardStorageService) Upload(ctx context.Context, user *models.SignedInUser, req *UploadRequest) error {
|
||||
upload, storagePath := s.tree.getRoot(getOrgId(user), req.Path)
|
||||
if upload == nil {
|
||||
return ErrUploadFeatureDisabled
|
||||
guardian := s.authService.newGuardian(ctx, user, getFirstSegment(req.Path))
|
||||
if !guardian.canWrite(req.Path) {
|
||||
return ErrAccessDenied
|
||||
}
|
||||
|
||||
if upload.Meta().ReadOnly {
|
||||
root, storagePath := s.tree.getRoot(getOrgId(user), req.Path)
|
||||
if root == nil {
|
||||
return ErrStorageNotFound
|
||||
}
|
||||
|
||||
if root.Meta().ReadOnly {
|
||||
return ErrUnsupportedStorage
|
||||
}
|
||||
|
||||
@@ -195,7 +236,7 @@ func (s *standardStorageService) Upload(ctx context.Context, user *models.Signed
|
||||
grafanaStorageLogger.Info("uploading a file", "filetype", req.MimeType, "path", req.Path)
|
||||
|
||||
if !req.OverwriteExistingFile {
|
||||
file, err := upload.Store().Get(ctx, storagePath)
|
||||
file, err := root.Store().Get(ctx, storagePath)
|
||||
if err != nil {
|
||||
grafanaStorageLogger.Error("failed while checking file existence", "err", err, "path", req.Path)
|
||||
return ErrUploadInternalError
|
||||
@@ -206,7 +247,7 @@ func (s *standardStorageService) Upload(ctx context.Context, user *models.Signed
|
||||
}
|
||||
}
|
||||
|
||||
if err := upload.Store().Upsert(ctx, upsertCommand); err != nil {
|
||||
if err := root.Store().Upsert(ctx, upsertCommand); err != nil {
|
||||
grafanaStorageLogger.Error("failed while uploading the file", "err", err, "path", req.Path)
|
||||
return ErrUploadInternalError
|
||||
}
|
||||
@@ -215,9 +256,14 @@ func (s *standardStorageService) Upload(ctx context.Context, user *models.Signed
|
||||
}
|
||||
|
||||
func (s *standardStorageService) DeleteFolder(ctx context.Context, user *models.SignedInUser, cmd *DeleteFolderCmd) error {
|
||||
guardian := s.authService.newGuardian(ctx, user, getFirstSegment(cmd.Path))
|
||||
if !guardian.canDelete(cmd.Path) {
|
||||
return ErrAccessDenied
|
||||
}
|
||||
|
||||
root, storagePath := s.tree.getRoot(getOrgId(user), cmd.Path)
|
||||
if root == nil {
|
||||
return fmt.Errorf("resources storage is not enabled")
|
||||
return ErrStorageNotFound
|
||||
}
|
||||
|
||||
if root.Meta().ReadOnly {
|
||||
@@ -227,13 +273,18 @@ func (s *standardStorageService) DeleteFolder(ctx context.Context, user *models.
|
||||
if storagePath == "" {
|
||||
storagePath = filestorage.Delimiter
|
||||
}
|
||||
return root.Store().DeleteFolder(ctx, storagePath, &filestorage.DeleteFolderOptions{Force: true})
|
||||
return root.Store().DeleteFolder(ctx, storagePath, &filestorage.DeleteFolderOptions{Force: true, AccessFilter: guardian.getPathFilter(ActionFilesDelete)})
|
||||
}
|
||||
|
||||
func (s *standardStorageService) CreateFolder(ctx context.Context, user *models.SignedInUser, cmd *CreateFolderCmd) error {
|
||||
guardian := s.authService.newGuardian(ctx, user, getFirstSegment(cmd.Path))
|
||||
if !guardian.canWrite(cmd.Path) {
|
||||
return ErrAccessDenied
|
||||
}
|
||||
|
||||
root, storagePath := s.tree.getRoot(getOrgId(user), cmd.Path)
|
||||
if root == nil {
|
||||
return fmt.Errorf("resources storage is not enabled")
|
||||
return ErrStorageNotFound
|
||||
}
|
||||
|
||||
if root.Meta().ReadOnly {
|
||||
@@ -248,9 +299,14 @@ func (s *standardStorageService) CreateFolder(ctx context.Context, user *models.
|
||||
}
|
||||
|
||||
func (s *standardStorageService) Delete(ctx context.Context, user *models.SignedInUser, path string) error {
|
||||
guardian := s.authService.newGuardian(ctx, user, getFirstSegment(path))
|
||||
if !guardian.canDelete(path) {
|
||||
return ErrAccessDenied
|
||||
}
|
||||
|
||||
root, storagePath := s.tree.getRoot(getOrgId(user), path)
|
||||
if root == nil {
|
||||
return fmt.Errorf("resources storage is not enabled")
|
||||
return ErrStorageNotFound
|
||||
}
|
||||
|
||||
if root.Meta().ReadOnly {
|
||||
|
||||
Reference in New Issue
Block a user