mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Unified Storage: Search permissions put behind feature flag (#99340)
* add feature flag * puts search permission filtering behind a feature flag * fixes issue with doc match id. When the match is for an in-memory index, the internal id is a string (this is what we expected). However, when its a file-based index, the internal id is a binary encoded int64 that point to something internally. So to get the id, we need to use ExternalID() instead of relying on the indexInternalID to be the correct format. * adds debug log * update comment * formatting
This commit is contained in:
parent
5b5831ae34
commit
dd483fc17f
@ -218,6 +218,7 @@ export interface FeatureToggles {
|
|||||||
rolePickerDrawer?: boolean;
|
rolePickerDrawer?: boolean;
|
||||||
unifiedStorageSearch?: boolean;
|
unifiedStorageSearch?: boolean;
|
||||||
unifiedStorageSearchSprinkles?: boolean;
|
unifiedStorageSearchSprinkles?: boolean;
|
||||||
|
unifiedStorageSearchPermissionFiltering?: boolean;
|
||||||
pluginsSriChecks?: boolean;
|
pluginsSriChecks?: boolean;
|
||||||
unifiedStorageBigObjectsSupport?: boolean;
|
unifiedStorageBigObjectsSupport?: boolean;
|
||||||
timeRangeProvider?: boolean;
|
timeRangeProvider?: boolean;
|
||||||
|
@ -1507,6 +1507,14 @@ var (
|
|||||||
HideFromDocs: true,
|
HideFromDocs: true,
|
||||||
HideFromAdminPage: true,
|
HideFromAdminPage: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "unifiedStorageSearchPermissionFiltering",
|
||||||
|
Description: "Enable permission filtering on unified storage search",
|
||||||
|
Stage: FeatureStageExperimental,
|
||||||
|
Owner: grafanaSearchAndStorageSquad,
|
||||||
|
HideFromDocs: true,
|
||||||
|
HideFromAdminPage: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "pluginsSriChecks",
|
Name: "pluginsSriChecks",
|
||||||
Description: "Enables SRI checks for plugin assets",
|
Description: "Enables SRI checks for plugin assets",
|
||||||
|
@ -199,6 +199,7 @@ useSessionStorageForRedirection,GA,@grafana/identity-access-team,false,false,fal
|
|||||||
rolePickerDrawer,experimental,@grafana/identity-access-team,false,false,false
|
rolePickerDrawer,experimental,@grafana/identity-access-team,false,false,false
|
||||||
unifiedStorageSearch,experimental,@grafana/search-and-storage,false,false,false
|
unifiedStorageSearch,experimental,@grafana/search-and-storage,false,false,false
|
||||||
unifiedStorageSearchSprinkles,experimental,@grafana/search-and-storage,false,false,false
|
unifiedStorageSearchSprinkles,experimental,@grafana/search-and-storage,false,false,false
|
||||||
|
unifiedStorageSearchPermissionFiltering,experimental,@grafana/search-and-storage,false,false,false
|
||||||
pluginsSriChecks,experimental,@grafana/plugins-platform-backend,false,false,false
|
pluginsSriChecks,experimental,@grafana/plugins-platform-backend,false,false,false
|
||||||
unifiedStorageBigObjectsSupport,experimental,@grafana/search-and-storage,false,false,false
|
unifiedStorageBigObjectsSupport,experimental,@grafana/search-and-storage,false,false,false
|
||||||
timeRangeProvider,experimental,@grafana/grafana-frontend-platform,false,false,false
|
timeRangeProvider,experimental,@grafana/grafana-frontend-platform,false,false,false
|
||||||
|
|
@ -807,6 +807,10 @@ const (
|
|||||||
// Enable sprinkles on unified storage search
|
// Enable sprinkles on unified storage search
|
||||||
FlagUnifiedStorageSearchSprinkles = "unifiedStorageSearchSprinkles"
|
FlagUnifiedStorageSearchSprinkles = "unifiedStorageSearchSprinkles"
|
||||||
|
|
||||||
|
// FlagUnifiedStorageSearchPermissionFiltering
|
||||||
|
// Enable permission filtering on unified storage search
|
||||||
|
FlagUnifiedStorageSearchPermissionFiltering = "unifiedStorageSearchPermissionFiltering"
|
||||||
|
|
||||||
// FlagPluginsSriChecks
|
// FlagPluginsSriChecks
|
||||||
// Enables SRI checks for plugin assets
|
// Enables SRI checks for plugin assets
|
||||||
FlagPluginsSriChecks = "pluginsSriChecks"
|
FlagPluginsSriChecks = "pluginsSriChecks"
|
||||||
|
@ -3882,6 +3882,20 @@
|
|||||||
"hideFromDocs": true
|
"hideFromDocs": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"name": "unifiedStorageSearchPermissionFiltering",
|
||||||
|
"resourceVersion": "1737489629408",
|
||||||
|
"creationTimestamp": "2025-01-21T20:00:29Z"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"description": "Enable permission filtering on unified storage search",
|
||||||
|
"stage": "experimental",
|
||||||
|
"codeowner": "@grafana/search-and-storage",
|
||||||
|
"hideFromAdminPage": true,
|
||||||
|
"hideFromDocs": true
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"name": "unifiedStorageSearchSprinkles",
|
"name": "unifiedStorageSearchSprinkles",
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/blevesearch/bleve/v2/search/query"
|
"github.com/blevesearch/bleve/v2/search/query"
|
||||||
bleveSearch "github.com/blevesearch/bleve/v2/search/searcher"
|
bleveSearch "github.com/blevesearch/bleve/v2/search/searcher"
|
||||||
index "github.com/blevesearch/bleve_index_api"
|
index "github.com/blevesearch/bleve_index_api"
|
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
"k8s.io/apimachinery/pkg/selection"
|
"k8s.io/apimachinery/pkg/selection"
|
||||||
|
|
||||||
@ -53,9 +54,11 @@ type bleveBackend struct {
|
|||||||
// cache info
|
// cache info
|
||||||
cache map[resource.NamespacedResource]*bleveIndex
|
cache map[resource.NamespacedResource]*bleveIndex
|
||||||
cacheMu sync.RWMutex
|
cacheMu sync.RWMutex
|
||||||
|
|
||||||
|
features featuremgmt.FeatureToggles
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBleveBackend(opts BleveOptions, tracer trace.Tracer) (*bleveBackend, error) {
|
func NewBleveBackend(opts BleveOptions, tracer trace.Tracer, features featuremgmt.FeatureToggles) (*bleveBackend, error) {
|
||||||
if opts.Root == "" {
|
if opts.Root == "" {
|
||||||
return nil, fmt.Errorf("bleve backend missing root folder configuration")
|
return nil, fmt.Errorf("bleve backend missing root folder configuration")
|
||||||
}
|
}
|
||||||
@ -68,11 +71,12 @@ func NewBleveBackend(opts BleveOptions, tracer trace.Tracer) (*bleveBackend, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &bleveBackend{
|
return &bleveBackend{
|
||||||
log: slog.Default().With("logger", "bleve-backend"),
|
log: slog.Default().With("logger", "bleve-backend"),
|
||||||
tracer: tracer,
|
tracer: tracer,
|
||||||
cache: make(map[resource.NamespacedResource]*bleveIndex),
|
cache: make(map[resource.NamespacedResource]*bleveIndex),
|
||||||
opts: opts,
|
opts: opts,
|
||||||
start: time.Now(),
|
start: time.Now(),
|
||||||
|
features: features,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,6 +176,7 @@ func (b *bleveBackend) BuildIndex(ctx context.Context,
|
|||||||
batchSize: b.opts.BatchSize,
|
batchSize: b.opts.BatchSize,
|
||||||
fields: fields,
|
fields: fields,
|
||||||
standard: resource.StandardSearchFields(),
|
standard: resource.StandardSearchFields(),
|
||||||
|
features: b.features,
|
||||||
}
|
}
|
||||||
|
|
||||||
idx.allFields, err = getAllFields(idx.standard, fields)
|
idx.allFields, err = getAllFields(idx.standard, fields)
|
||||||
@ -243,6 +248,8 @@ type bleveIndex struct {
|
|||||||
// only valid in single thread
|
// only valid in single thread
|
||||||
batch *bleve.Batch
|
batch *bleve.Batch
|
||||||
batchSize int // ??? not totally sure the units here
|
batchSize int // ??? not totally sure the units here
|
||||||
|
|
||||||
|
features featuremgmt.FeatureToggles
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write implements resource.DocumentIndex.
|
// Write implements resource.DocumentIndex.
|
||||||
@ -579,8 +586,7 @@ func (b *bleveIndex) toBleveSearchRequest(ctx context.Context, req *resource.Res
|
|||||||
searchrequest.Query = bleve.NewConjunctionQuery(queries...) // AND
|
searchrequest.Query = bleve.NewConjunctionQuery(queries...) // AND
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can we remove this? Is access ever nil?
|
if access != nil && b.features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorageSearchPermissionFiltering) {
|
||||||
if access != nil {
|
|
||||||
auth, ok := authlib.AuthInfoFrom(ctx)
|
auth, ok := authlib.AuthInfoFrom(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, resource.AsErrorResult(fmt.Errorf("missing auth info"))
|
return nil, resource.AsErrorResult(fmt.Errorf("missing auth info"))
|
||||||
@ -883,13 +889,20 @@ func (q *permissionScopedQuery) Searcher(ctx context.Context, i index.IndexReade
|
|||||||
}
|
}
|
||||||
|
|
||||||
filteringSearcher := bleveSearch.NewFilteringSearcher(ctx, searcher, func(d *search.DocumentMatch) bool {
|
filteringSearcher := bleveSearch.NewFilteringSearcher(ctx, searcher, func(d *search.DocumentMatch) bool {
|
||||||
// The internal ID has the format: <namespace>/<group>/<resourceType>/<name>
|
// The doc ID has the format: <namespace>/<group>/<resourceType>/<name>
|
||||||
// Only the internal ID is present on the document match here. Need to use the dvReader for any other fields.
|
// IndexInternalID will be the same as the doc ID when using an in-memory index, but when using a file-based
|
||||||
id := string(d.IndexInternalID)
|
// index it becomes a binary encoded number that has some other internal meaning. Using ExternalID() will get the
|
||||||
parts := strings.Split(id, "/")
|
// correct doc ID regardless of the index type.
|
||||||
|
d.ID, err = i.ExternalID(d.IndexInternalID)
|
||||||
|
if err != nil {
|
||||||
|
q.log.Debug("Error getting external ID", "error", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(d.ID, "/")
|
||||||
// Exclude doc if id isn't expected format
|
// Exclude doc if id isn't expected format
|
||||||
if len(parts) != 4 {
|
if len(parts) != 4 {
|
||||||
q.log.Debug("Unexpected document ID format", "id", id)
|
q.log.Debug("Unexpected document ID format", "id", d.ID)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
ns := parts[0]
|
ns := parts[0]
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ func TestBleveBackend(t *testing.T) {
|
|||||||
backend, err := NewBleveBackend(BleveOptions{
|
backend, err := NewBleveBackend(BleveOptions{
|
||||||
Root: tmpdir,
|
Root: tmpdir,
|
||||||
FileThreshold: 5, // with more than 5 items we create a file on disk
|
FileThreshold: 5, // with more than 5 items we create a file on disk
|
||||||
}, tracing.NewNoopTracerService())
|
}, tracing.NewNoopTracerService(), featuremgmt.WithFeatures(featuremgmt.FlagUnifiedStorageSearchPermissionFiltering))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// AVOID NPE in test
|
// AVOID NPE in test
|
||||||
|
@ -70,7 +70,8 @@ func NewResourceServer(ctx context.Context, db infraDB.DB, cfg *setting.Cfg,
|
|||||||
Root: root,
|
Root: root,
|
||||||
FileThreshold: int64(cfg.IndexFileThreshold), // fewer than X items will use a memory index
|
FileThreshold: int64(cfg.IndexFileThreshold), // fewer than X items will use a memory index
|
||||||
BatchSize: cfg.IndexMaxBatchSize, // This is the batch size for how many objects to add to the index at once
|
BatchSize: cfg.IndexMaxBatchSize, // This is the batch size for how many objects to add to the index at once
|
||||||
}, tracer)
|
}, tracer, features)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user