mirror of
https://github.com/grafana/grafana.git
synced 2026-08-18 17:15:08 -05:00
AccessControl: Resolve attribute based scopes to id based scopes (#40742)
* AccessControl: POC scope attribute resolution Refactor based on ScopeMutators test errors and calls to cache Add comments to tests Rename logger Create keywordMutator only once * AccessControl: Add AttributeScopeResolver registration Co-authored-by: gamab <gabriel.mabille@grafana.com> * AccessControl: Add AttributeScopeResolver to datasources Co-authored-by: gamab <gabriel.mabille@grafana.com> * Test evaluation with translation * fix imports * AccessControl: Test attribute resolver * Fix trailing white space * Make ScopeResolver public for enterprise redefine * Handle wildcard Co-authored-by: Jguer <joao.guerreiro@grafana.com> Co-authored-by: jguer <joao.guerreiro@grafana.com>
This commit is contained in:
co-authored by
gamab
Jguer
parent
7a622422a9
commit
54280fc9d7
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/httpclient"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@@ -49,7 +51,7 @@ type cachedDecryptedJSON struct {
|
||||
json map[string]string
|
||||
}
|
||||
|
||||
func ProvideService(bus bus.Bus, store *sqlstore.SQLStore, secretsService secrets.Service) *Service {
|
||||
func ProvideService(bus bus.Bus, store *sqlstore.SQLStore, secretsService secrets.Service, ac accesscontrol.AccessControl) *Service {
|
||||
s := &Service{
|
||||
Bus: bus,
|
||||
SQLStore: store,
|
||||
@@ -70,9 +72,41 @@ func ProvideService(bus bus.Bus, store *sqlstore.SQLStore, secretsService secret
|
||||
s.Bus.AddHandler(s.UpdateDataSource)
|
||||
s.Bus.AddHandler(s.GetDefaultDataSource)
|
||||
|
||||
ac.RegisterAttributeScopeResolver(NewNameScopeResolver(store))
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
type DataSourceRetriever interface {
|
||||
GetDataSource(ctx context.Context, query *models.GetDataSourceQuery) error
|
||||
}
|
||||
|
||||
// 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) {
|
||||
dsNameResolver := func(ctx context.Context, orgID int64, initialScope string) (string, error) {
|
||||
dsNames := strings.Split(initialScope, ":")
|
||||
if dsNames[0] != "datasources" || len(dsNames) != 3 {
|
||||
return "", accesscontrol.ErrInvalidScope
|
||||
}
|
||||
|
||||
dsName := dsNames[2]
|
||||
// Special wildcard case
|
||||
if dsName == "*" {
|
||||
return accesscontrol.Scope("datasources", "id", "*"), nil
|
||||
}
|
||||
|
||||
query := models.GetDataSourceQuery{Name: dsName, OrgId: orgID}
|
||||
if err := db.GetDataSource(ctx, &query); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return accesscontrol.Scope("datasources", "id", fmt.Sprintf("%v", query.Result.Id)), nil
|
||||
}
|
||||
|
||||
return "datasources:name:", dsNameResolver
|
||||
}
|
||||
|
||||
func (s *Service) GetDataSource(ctx context.Context, query *models.GetDataSourceQuery) error {
|
||||
return s.SQLStore.GetDataSource(ctx, query)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/httpclient"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/database"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/fakes"
|
||||
@@ -34,7 +36,7 @@ func TestService(t *testing.T) {
|
||||
})
|
||||
|
||||
secretsService := secretsManager.SetupTestService(t, database.ProvideSecretsStore(sqlStore))
|
||||
s := ProvideService(bus.New(), sqlStore, secretsService)
|
||||
s := ProvideService(bus.New(), sqlStore, secretsService, &acmock.Mock{})
|
||||
|
||||
var ds *models.DataSource
|
||||
|
||||
@@ -66,6 +68,72 @@ func TestService(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
type dataSourceMockRetriever struct {
|
||||
res *models.DataSource
|
||||
}
|
||||
|
||||
func (d *dataSourceMockRetriever) GetDataSource(ctx context.Context, query *models.GetDataSourceQuery) error {
|
||||
if query.Name == d.res.Name {
|
||||
query.Result = d.res
|
||||
|
||||
return nil
|
||||
}
|
||||
return models.ErrDataSourceNotFound
|
||||
}
|
||||
|
||||
func TestService_NameScopeResolver(t *testing.T) {
|
||||
type testCaseResolver struct {
|
||||
desc string
|
||||
given string
|
||||
want string
|
||||
wantErr error
|
||||
}
|
||||
|
||||
testCases := []testCaseResolver{
|
||||
{
|
||||
desc: "correct",
|
||||
given: "datasources:name:test-datasource",
|
||||
want: "datasources:id:1",
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "correct",
|
||||
given: "datasources:name:*",
|
||||
want: "datasources:id:*",
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "unknown datasource",
|
||||
given: "datasources:name:unknown-datasource",
|
||||
want: "",
|
||||
wantErr: models.ErrDataSourceNotFound,
|
||||
},
|
||||
{
|
||||
desc: "malformed scope",
|
||||
given: "datasources:unknown-datasource",
|
||||
want: "",
|
||||
wantErr: accesscontrol.ErrInvalidScope,
|
||||
},
|
||||
}
|
||||
|
||||
testDataSource := &models.DataSource{Id: 1, Name: "test-datasource"}
|
||||
prefix, resolver := NewNameScopeResolver(&dataSourceMockRetriever{testDataSource})
|
||||
require.Equal(t, "datasources:name:", prefix)
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
resolved, err := resolver(context.Background(), 1, tc.given)
|
||||
if tc.wantErr != nil {
|
||||
require.Error(t, err)
|
||||
require.Equal(t, tc.wantErr, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.want, resolved)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:goconst
|
||||
func TestService_GetHttpTransport(t *testing.T) {
|
||||
t.Run("Should use cached proxy", func(t *testing.T) {
|
||||
@@ -83,7 +151,7 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
}
|
||||
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := ProvideService(bus.New(), nil, secretsService)
|
||||
dsService := ProvideService(bus.New(), nil, secretsService, &acmock.Mock{})
|
||||
|
||||
rt1, err := dsService.GetHTTPTransport(&ds, provider)
|
||||
require.NoError(t, err)
|
||||
@@ -116,7 +184,7 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
json.Set("tlsAuthWithCACert", true)
|
||||
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := ProvideService(bus.New(), nil, secretsService)
|
||||
dsService := ProvideService(bus.New(), nil, secretsService, &acmock.Mock{})
|
||||
|
||||
tlsCaCert, err := secretsService.Encrypt(context.Background(), []byte(caCert), secrets.WithoutScope())
|
||||
require.NoError(t, err)
|
||||
@@ -166,7 +234,7 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
json.Set("tlsAuth", true)
|
||||
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := ProvideService(bus.New(), nil, secretsService)
|
||||
dsService := ProvideService(bus.New(), nil, secretsService, &acmock.Mock{})
|
||||
|
||||
tlsClientCert, err := secretsService.Encrypt(context.Background(), []byte(clientCert), secrets.WithoutScope())
|
||||
require.NoError(t, err)
|
||||
@@ -209,7 +277,7 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
json.Set("serverName", "server-name")
|
||||
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := ProvideService(bus.New(), nil, secretsService)
|
||||
dsService := ProvideService(bus.New(), nil, secretsService, &acmock.Mock{})
|
||||
|
||||
tlsCaCert, err := secretsService.Encrypt(context.Background(), []byte(caCert), secrets.WithoutScope())
|
||||
require.NoError(t, err)
|
||||
@@ -246,7 +314,7 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
json.Set("tlsSkipVerify", true)
|
||||
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := ProvideService(bus.New(), nil, secretsService)
|
||||
dsService := ProvideService(bus.New(), nil, secretsService, &acmock.Mock{})
|
||||
|
||||
ds := models.DataSource{
|
||||
Id: 1,
|
||||
@@ -277,7 +345,7 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
})
|
||||
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := ProvideService(bus.New(), nil, secretsService)
|
||||
dsService := ProvideService(bus.New(), nil, secretsService, &acmock.Mock{})
|
||||
|
||||
encryptedData, err := secretsService.Encrypt(context.Background(), []byte(`Bearer xf5yhfkpsnmgo`), secrets.WithoutScope())
|
||||
require.NoError(t, err)
|
||||
@@ -336,7 +404,7 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
})
|
||||
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := ProvideService(bus.New(), nil, secretsService)
|
||||
dsService := ProvideService(bus.New(), nil, secretsService, &acmock.Mock{})
|
||||
|
||||
ds := models.DataSource{
|
||||
Id: 1,
|
||||
@@ -369,7 +437,7 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := ProvideService(bus.New(), nil, secretsService)
|
||||
dsService := ProvideService(bus.New(), nil, secretsService, &acmock.Mock{})
|
||||
|
||||
ds := models.DataSource{
|
||||
Type: models.DS_ES,
|
||||
@@ -403,7 +471,7 @@ func TestService_getTimeout(t *testing.T) {
|
||||
}
|
||||
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := ProvideService(bus.New(), nil, secretsService)
|
||||
dsService := ProvideService(bus.New(), nil, secretsService, &acmock.Mock{})
|
||||
|
||||
for _, tc := range testCases {
|
||||
ds := &models.DataSource{
|
||||
@@ -416,7 +484,7 @@ func TestService_getTimeout(t *testing.T) {
|
||||
func TestService_DecryptedValue(t *testing.T) {
|
||||
t.Run("When datasource hasn't been updated, encrypted JSON should be fetched from cache", func(t *testing.T) {
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := ProvideService(bus.New(), nil, secretsService)
|
||||
dsService := ProvideService(bus.New(), nil, secretsService, &acmock.Mock{})
|
||||
|
||||
encryptedJsonData, err := secretsService.EncryptJsonData(
|
||||
context.Background(),
|
||||
@@ -470,7 +538,7 @@ func TestService_DecryptedValue(t *testing.T) {
|
||||
SecureJsonData: encryptedJsonData,
|
||||
}
|
||||
|
||||
dsService := ProvideService(bus.New(), nil, secretsService)
|
||||
dsService := ProvideService(bus.New(), nil, secretsService, &acmock.Mock{})
|
||||
|
||||
// Populate cache
|
||||
password, ok := dsService.DecryptedValue(&ds, "password")
|
||||
@@ -506,7 +574,7 @@ func TestService_HTTPClientOptions(t *testing.T) {
|
||||
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
|
||||
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := ProvideService(bus.New(), nil, secretsService)
|
||||
dsService := ProvideService(bus.New(), nil, secretsService, &acmock.Mock{})
|
||||
|
||||
opts, err := dsService.httpClientOptions(&ds)
|
||||
require.NoError(t, err)
|
||||
@@ -523,7 +591,7 @@ func TestService_HTTPClientOptions(t *testing.T) {
|
||||
})
|
||||
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := ProvideService(bus.New(), nil, secretsService)
|
||||
dsService := ProvideService(bus.New(), nil, secretsService, &acmock.Mock{})
|
||||
|
||||
opts, err := dsService.httpClientOptions(&ds)
|
||||
require.NoError(t, err)
|
||||
@@ -543,7 +611,7 @@ func TestService_HTTPClientOptions(t *testing.T) {
|
||||
})
|
||||
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := ProvideService(bus.New(), nil, secretsService)
|
||||
dsService := ProvideService(bus.New(), nil, secretsService, &acmock.Mock{})
|
||||
|
||||
opts, err := dsService.httpClientOptions(&ds)
|
||||
require.NoError(t, err)
|
||||
@@ -567,7 +635,7 @@ func TestService_HTTPClientOptions(t *testing.T) {
|
||||
})
|
||||
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := ProvideService(bus.New(), nil, secretsService)
|
||||
dsService := ProvideService(bus.New(), nil, secretsService, &acmock.Mock{})
|
||||
|
||||
opts, err := dsService.httpClientOptions(&ds)
|
||||
require.NoError(t, err)
|
||||
@@ -585,7 +653,7 @@ func TestService_HTTPClientOptions(t *testing.T) {
|
||||
})
|
||||
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := ProvideService(bus.New(), nil, secretsService)
|
||||
dsService := ProvideService(bus.New(), nil, secretsService, &acmock.Mock{})
|
||||
|
||||
_, err := dsService.httpClientOptions(&ds)
|
||||
assert.Error(t, err)
|
||||
@@ -599,7 +667,7 @@ func TestService_HTTPClientOptions(t *testing.T) {
|
||||
})
|
||||
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := ProvideService(bus.New(), nil, secretsService)
|
||||
dsService := ProvideService(bus.New(), nil, secretsService, &acmock.Mock{})
|
||||
|
||||
opts, err := dsService.httpClientOptions(&ds)
|
||||
require.NoError(t, err)
|
||||
|
||||
Reference in New Issue
Block a user