mirror of https://github.com/grafana/grafana.git
unistore: wire the authz client (#96632)
* unistore: wire the authz client * rename dashboards.grafana.app into dashboard.grafana.app * wire the authz client * wire the authz client * resuse the Standalone constructor * configure default migration for resource folder * add tests * cleanup * add logging
This commit is contained in:
parent
6571451a57
commit
e270412dbf
|
@ -17,7 +17,7 @@ func (a RuntimeConfig) String() string {
|
||||||
|
|
||||||
// Supported options are:
|
// Supported options are:
|
||||||
//
|
//
|
||||||
// <group>/<version>=true|false for a specific API group and version (e.g. dashboards.grafana.app/v0alpha1=true)
|
// <group>/<version>=true|false for a specific API group and version (e.g. dashboard.grafana.app/v0alpha1=true)
|
||||||
// api/all=true|false controls all API versions
|
// api/all=true|false controls all API versions
|
||||||
// api/ga=true|false controls all API versions of the form v[0-9]+
|
// api/ga=true|false controls all API versions of the form v[0-9]+
|
||||||
// api/beta=true|false controls all API versions of the form v[0-9]+beta[0-9]+
|
// api/beta=true|false controls all API versions of the form v[0-9]+beta[0-9]+
|
||||||
|
|
|
@ -8,11 +8,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestReadRuntimeCOnfig(t *testing.T) {
|
func TestReadRuntimeCOnfig(t *testing.T) {
|
||||||
out, err := ReadRuntimeConfig("all/all=true,dashboards.grafana.app/v0alpha1=false")
|
out, err := ReadRuntimeConfig("all/all=true,dashboard.grafana.app/v0alpha1=false")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, []RuntimeConfig{
|
require.Equal(t, []RuntimeConfig{
|
||||||
{Group: "all", Version: "all", Enabled: true},
|
{Group: "all", Version: "all", Enabled: true},
|
||||||
{Group: "dashboards.grafana.app", Version: "v0alpha1", Enabled: false},
|
{Group: "dashboard.grafana.app", Version: "v0alpha1", Enabled: false},
|
||||||
}, out)
|
}, out)
|
||||||
require.Equal(t, "all/all=true", fmt.Sprintf("%v", out[0]))
|
require.Equal(t, "all/all=true", fmt.Sprintf("%v", out[0]))
|
||||||
|
|
||||||
|
|
|
@ -88,7 +88,10 @@ func ProvideStandaloneAuthZClient(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return newGrpcLegacyClient(authCfg)
|
if cfg.StackID == "" {
|
||||||
|
return newGrpcLegacyClient(authCfg)
|
||||||
|
}
|
||||||
|
return newCloudLegacyClient(authCfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newInProcLegacyClient(server *legacyServer) (authzlib.AccessChecker, error) {
|
func newInProcLegacyClient(server *legacyServer) (authzlib.AccessChecker, error) {
|
||||||
|
|
|
@ -2,10 +2,10 @@ package mappers
|
||||||
|
|
||||||
type VerbToAction map[string]string // e.g. "get" -> "read"
|
type VerbToAction map[string]string // e.g. "get" -> "read"
|
||||||
type ResourceVerbToAction map[string]VerbToAction // e.g. "dashboards" -> VerbToAction
|
type ResourceVerbToAction map[string]VerbToAction // e.g. "dashboards" -> VerbToAction
|
||||||
type GroupResourceVerbToAction map[string]ResourceVerbToAction // e.g. "dashboards.grafana.app" -> ResourceVerbToAction
|
type GroupResourceVerbToAction map[string]ResourceVerbToAction // e.g. "dashboard.grafana.app" -> ResourceVerbToAction
|
||||||
|
|
||||||
type ResourceToAttribute map[string]string // e.g. "dashboards" -> "uid"
|
type ResourceToAttribute map[string]string // e.g. "dashboards" -> "uid"
|
||||||
type GroupResourceToAttribute map[string]ResourceToAttribute // e.g. "dashboards.grafana.app" -> ResourceToAttribute
|
type GroupResourceToAttribute map[string]ResourceToAttribute // e.g. "dashboard.grafana.app" -> ResourceToAttribute
|
||||||
|
|
||||||
type K8sRbacMapper struct {
|
type K8sRbacMapper struct {
|
||||||
DefaultActions VerbToAction
|
DefaultActions VerbToAction
|
||||||
|
@ -28,8 +28,8 @@ func NewK8sRbacMapper() *K8sRbacMapper {
|
||||||
},
|
},
|
||||||
DefaultAttribute: "uid",
|
DefaultAttribute: "uid",
|
||||||
Actions: GroupResourceVerbToAction{
|
Actions: GroupResourceVerbToAction{
|
||||||
"dashboards.grafana.app": ResourceVerbToAction{"dashboards": VerbToAction{}},
|
"dashboard.grafana.app": ResourceVerbToAction{"dashboards": VerbToAction{}},
|
||||||
"folders.grafana.app": ResourceVerbToAction{"folders": VerbToAction{}},
|
"folder.grafana.app": ResourceVerbToAction{"folders": VerbToAction{}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ func Test_legacyServer_Check(t *testing.T) {
|
||||||
req: &authzv1.CheckRequest{
|
req: &authzv1.CheckRequest{
|
||||||
Subject: "user:1",
|
Subject: "user:1",
|
||||||
Verb: "get",
|
Verb: "get",
|
||||||
Group: "dashboards.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
Name: "dash1",
|
Name: "dash1",
|
||||||
Namespace: "org-2",
|
Namespace: "org-2",
|
||||||
|
@ -74,7 +74,7 @@ func Test_legacyServer_Check(t *testing.T) {
|
||||||
req: &authzv1.CheckRequest{
|
req: &authzv1.CheckRequest{
|
||||||
Subject: "user:1",
|
Subject: "user:1",
|
||||||
Verb: "get",
|
Verb: "get",
|
||||||
Group: "dashboards.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
Name: "dash1",
|
Name: "dash1",
|
||||||
Namespace: "org-2",
|
Namespace: "org-2",
|
||||||
|
@ -88,7 +88,7 @@ func Test_legacyServer_Check(t *testing.T) {
|
||||||
req: &authzv1.CheckRequest{
|
req: &authzv1.CheckRequest{
|
||||||
Subject: "user:1",
|
Subject: "user:1",
|
||||||
Verb: "get",
|
Verb: "get",
|
||||||
Group: "dashboards.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
Name: "dash1",
|
Name: "dash1",
|
||||||
Namespace: "org-2",
|
Namespace: "org-2",
|
||||||
|
@ -106,7 +106,7 @@ func Test_legacyServer_Check(t *testing.T) {
|
||||||
req: &authzv1.CheckRequest{
|
req: &authzv1.CheckRequest{
|
||||||
Subject: "user:1",
|
Subject: "user:1",
|
||||||
Verb: "get",
|
Verb: "get",
|
||||||
Group: "dashboards.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
Namespace: "org-2",
|
Namespace: "org-2",
|
||||||
},
|
},
|
||||||
|
@ -131,7 +131,7 @@ func Test_legacyServer_Check(t *testing.T) {
|
||||||
req: &authzv1.CheckRequest{
|
req: &authzv1.CheckRequest{
|
||||||
Subject: "user:1",
|
Subject: "user:1",
|
||||||
Verb: "get",
|
Verb: "get",
|
||||||
Group: "dashboards.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Name: "dash1",
|
Name: "dash1",
|
||||||
Namespace: "org-2",
|
Namespace: "org-2",
|
||||||
},
|
},
|
||||||
|
@ -141,7 +141,7 @@ func Test_legacyServer_Check(t *testing.T) {
|
||||||
name: "should return error when verb is not set",
|
name: "should return error when verb is not set",
|
||||||
req: &authzv1.CheckRequest{
|
req: &authzv1.CheckRequest{
|
||||||
Subject: "user:1",
|
Subject: "user:1",
|
||||||
Group: "dashboards.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
Name: "dash1",
|
Name: "dash1",
|
||||||
Namespace: "org-2",
|
Namespace: "org-2",
|
||||||
|
@ -152,7 +152,7 @@ func Test_legacyServer_Check(t *testing.T) {
|
||||||
name: "should return error when subject is not set",
|
name: "should return error when subject is not set",
|
||||||
req: &authzv1.CheckRequest{
|
req: &authzv1.CheckRequest{
|
||||||
Verb: "get",
|
Verb: "get",
|
||||||
Group: "dashboards.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
Name: "dash1",
|
Name: "dash1",
|
||||||
Namespace: "org-2",
|
Namespace: "org-2",
|
||||||
|
@ -164,7 +164,7 @@ func Test_legacyServer_Check(t *testing.T) {
|
||||||
req: &authzv1.CheckRequest{
|
req: &authzv1.CheckRequest{
|
||||||
Subject: "user:1",
|
Subject: "user:1",
|
||||||
Verb: "get",
|
Verb: "get",
|
||||||
Group: "dashboards.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
Name: "dash1",
|
Name: "dash1",
|
||||||
Namespace: "stacks-2",
|
Namespace: "stacks-2",
|
||||||
|
|
|
@ -31,7 +31,7 @@ func newBatch(subject, group, resource string, items []*authzextv1.BatchCheckIte
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBatchCheck(t *testing.T, server *Server) {
|
func testBatchCheck(t *testing.T, server *Server) {
|
||||||
t.Run("user:1 should only be able to read resource:dashboards.grafana.app/dashboards/1", func(t *testing.T) {
|
t.Run("user:1 should only be able to read resource:dashboard.grafana.app/dashboards/1", func(t *testing.T) {
|
||||||
groupPrefix := zanzana.FormatGroupResource(dashboardGroup, dashboardResource)
|
groupPrefix := zanzana.FormatGroupResource(dashboardGroup, dashboardResource)
|
||||||
res, err := server.BatchCheck(context.Background(), newBatch("user:1", dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{
|
res, err := server.BatchCheck(context.Background(), newBatch("user:1", dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{
|
||||||
{Name: "1", Folder: "1"},
|
{Name: "1", Folder: "1"},
|
||||||
|
@ -44,7 +44,7 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||||
assert.False(t, res.Groups[groupPrefix].Items["2"])
|
assert.False(t, res.Groups[groupPrefix].Items["2"])
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("user:2 should be able to read resource:dashboards.grafana.app/dashboards/{1,2} through namespace", func(t *testing.T) {
|
t.Run("user:2 should be able to read resource:dashboard.grafana.app/dashboards/{1,2} through namespace", func(t *testing.T) {
|
||||||
groupPrefix := zanzana.FormatGroupResource(dashboardGroup, dashboardResource)
|
groupPrefix := zanzana.FormatGroupResource(dashboardGroup, dashboardResource)
|
||||||
res, err := server.BatchCheck(context.Background(), newBatch("user:2", dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{
|
res, err := server.BatchCheck(context.Background(), newBatch("user:2", dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{
|
||||||
{Name: "1", Folder: "1"},
|
{Name: "1", Folder: "1"},
|
||||||
|
@ -54,7 +54,7 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||||
assert.Len(t, res.Groups[groupPrefix].Items, 2)
|
assert.Len(t, res.Groups[groupPrefix].Items, 2)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("user:3 should be able to read resource:dashboards.grafana.app/dashboards/1 with set relation", func(t *testing.T) {
|
t.Run("user:3 should be able to read resource:dashboard.grafana.app/dashboards/1 with set relation", func(t *testing.T) {
|
||||||
groupPrefix := zanzana.FormatGroupResource(dashboardGroup, dashboardResource)
|
groupPrefix := zanzana.FormatGroupResource(dashboardGroup, dashboardResource)
|
||||||
res, err := server.BatchCheck(context.Background(), newBatch("user:3", dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{
|
res, err := server.BatchCheck(context.Background(), newBatch("user:3", dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{
|
||||||
{Name: "1", Folder: "1"},
|
{Name: "1", Folder: "1"},
|
||||||
|
@ -67,7 +67,7 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||||
assert.False(t, res.Groups[groupPrefix].Items["2"])
|
assert.False(t, res.Groups[groupPrefix].Items["2"])
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("user:4 should be able to read all dashboards.grafana.app/dashboards in folder 1 and 3", func(t *testing.T) {
|
t.Run("user:4 should be able to read all dashboard.grafana.app/dashboards in folder 1 and 3", func(t *testing.T) {
|
||||||
groupPrefix := zanzana.FormatGroupResource(dashboardGroup, dashboardResource)
|
groupPrefix := zanzana.FormatGroupResource(dashboardGroup, dashboardResource)
|
||||||
res, err := server.BatchCheck(context.Background(), newBatch("user:4", dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{
|
res, err := server.BatchCheck(context.Background(), newBatch("user:4", dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{
|
||||||
{Name: "1", Folder: "1"},
|
{Name: "1", Folder: "1"},
|
||||||
|
@ -82,7 +82,7 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||||
assert.False(t, res.Groups[groupPrefix].Items["3"])
|
assert.False(t, res.Groups[groupPrefix].Items["3"])
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("user:5 should be able to read resource:dashboards.grafana.app/dashboards/1 through folder with set relation", func(t *testing.T) {
|
t.Run("user:5 should be able to read resource:dashboard.grafana.app/dashboards/1 through folder with set relation", func(t *testing.T) {
|
||||||
groupPrefix := zanzana.FormatGroupResource(dashboardGroup, dashboardResource)
|
groupPrefix := zanzana.FormatGroupResource(dashboardGroup, dashboardResource)
|
||||||
res, err := server.BatchCheck(context.Background(), newBatch("user:5", dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{
|
res, err := server.BatchCheck(context.Background(), newBatch("user:5", dashboardGroup, dashboardResource, []*authzextv1.BatchCheckItem{
|
||||||
{Name: "1", Folder: "1"},
|
{Name: "1", Folder: "1"},
|
||||||
|
|
|
@ -24,7 +24,7 @@ func testCheck(t *testing.T, server *Server) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("user:1 should only be able to read resource:dashboards.grafana.app/dashboards/1", func(t *testing.T) {
|
t.Run("user:1 should only be able to read resource:dashboard.grafana.app/dashboards/1", func(t *testing.T) {
|
||||||
res, err := server.Check(context.Background(), newRead("user:1", dashboardGroup, dashboardResource, "1", "1"))
|
res, err := server.Check(context.Background(), newRead("user:1", dashboardGroup, dashboardResource, "1", "1"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.True(t, res.GetAllowed())
|
assert.True(t, res.GetAllowed())
|
||||||
|
@ -35,13 +35,13 @@ func testCheck(t *testing.T, server *Server) {
|
||||||
assert.False(t, res.GetAllowed())
|
assert.False(t, res.GetAllowed())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("user:2 should be able to read resource:dashboards.grafana.app/dashboards/1 through namespace", func(t *testing.T) {
|
t.Run("user:2 should be able to read resource:dashboard.grafana.app/dashboards/1 through namespace", func(t *testing.T) {
|
||||||
res, err := server.Check(context.Background(), newRead("user:2", dashboardGroup, dashboardResource, "1", "1"))
|
res, err := server.Check(context.Background(), newRead("user:2", dashboardGroup, dashboardResource, "1", "1"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.True(t, res.GetAllowed())
|
assert.True(t, res.GetAllowed())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("user:3 should be able to read resource:dashboards.grafana.app/dashboards/1 with set relation", func(t *testing.T) {
|
t.Run("user:3 should be able to read resource:dashboard.grafana.app/dashboards/1 with set relation", func(t *testing.T) {
|
||||||
res, err := server.Check(context.Background(), newRead("user:3", dashboardGroup, dashboardResource, "1", "1"))
|
res, err := server.Check(context.Background(), newRead("user:3", dashboardGroup, dashboardResource, "1", "1"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.True(t, res.GetAllowed())
|
assert.True(t, res.GetAllowed())
|
||||||
|
@ -52,7 +52,7 @@ func testCheck(t *testing.T, server *Server) {
|
||||||
assert.False(t, res.GetAllowed())
|
assert.False(t, res.GetAllowed())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("user:4 should be able to read all dashboards.grafana.app/dashboards in folder 1 and 3", func(t *testing.T) {
|
t.Run("user:4 should be able to read all dashboard.grafana.app/dashboards in folder 1 and 3", func(t *testing.T) {
|
||||||
res, err := server.Check(context.Background(), newRead("user:4", dashboardGroup, dashboardResource, "1", "1"))
|
res, err := server.Check(context.Background(), newRead("user:4", dashboardGroup, dashboardResource, "1", "1"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.True(t, res.GetAllowed())
|
assert.True(t, res.GetAllowed())
|
||||||
|
@ -71,7 +71,7 @@ func testCheck(t *testing.T, server *Server) {
|
||||||
assert.False(t, res.GetAllowed())
|
assert.False(t, res.GetAllowed())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("user:5 should be able to read resource:dashboards.grafana.app/dashboards/1 through folder with set relation", func(t *testing.T) {
|
t.Run("user:5 should be able to read resource:dashboard.grafana.app/dashboards/1 through folder with set relation", func(t *testing.T) {
|
||||||
res, err := server.Check(context.Background(), newRead("user:5", dashboardGroup, dashboardResource, "1", "1"))
|
res, err := server.Check(context.Background(), newRead("user:5", dashboardGroup, dashboardResource, "1", "1"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.True(t, res.GetAllowed())
|
assert.True(t, res.GetAllowed())
|
||||||
|
|
|
@ -22,7 +22,7 @@ func testList(t *testing.T, server *Server) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("user:1 should list resource:dashboards.grafana.app/dashboards/1", func(t *testing.T) {
|
t.Run("user:1 should list resource:dashboard.grafana.app/dashboards/1", func(t *testing.T) {
|
||||||
res, err := server.List(context.Background(), newList("user:1", dashboardGroup, dashboardResource))
|
res, err := server.List(context.Background(), newList("user:1", dashboardGroup, dashboardResource))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, res.GetItems(), 1)
|
assert.Len(t, res.GetItems(), 1)
|
||||||
|
@ -38,7 +38,7 @@ func testList(t *testing.T, server *Server) {
|
||||||
assert.Len(t, res.GetFolders(), 0)
|
assert.Len(t, res.GetFolders(), 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("user:3 should be able to list resource:dashboards.grafana.app/dashboards/1 with set relation", func(t *testing.T) {
|
t.Run("user:3 should be able to list resource:dashboard.grafana.app/dashboards/1 with set relation", func(t *testing.T) {
|
||||||
res, err := server.List(context.Background(), newList("user:3", dashboardGroup, dashboardResource))
|
res, err := server.List(context.Background(), newList("user:3", dashboardGroup, dashboardResource))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ func testList(t *testing.T, server *Server) {
|
||||||
assert.Equal(t, res.GetItems()[0], "1")
|
assert.Equal(t, res.GetItems()[0], "1")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("user:4 should be able to list all dashboards.grafana.app/dashboards in folder 1 and 3", func(t *testing.T) {
|
t.Run("user:4 should be able to list all dashboard.grafana.app/dashboards in folder 1 and 3", func(t *testing.T) {
|
||||||
res, err := server.List(context.Background(), newList("user:4", dashboardGroup, dashboardResource))
|
res, err := server.List(context.Background(), newList("user:4", dashboardGroup, dashboardResource))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, res.GetItems(), 0)
|
assert.Len(t, res.GetItems(), 0)
|
||||||
|
@ -64,7 +64,7 @@ func testList(t *testing.T, server *Server) {
|
||||||
assert.Equal(t, second, "3")
|
assert.Equal(t, second, "3")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("user:5 should be get list all dashboards.grafana.app/dashboards in folder 1 with set relation", func(t *testing.T) {
|
t.Run("user:5 should be get list all dashboard.grafana.app/dashboards in folder 1 with set relation", func(t *testing.T) {
|
||||||
res, err := server.List(context.Background(), newList("user:5", dashboardGroup, dashboardResource))
|
res, err := server.List(context.Background(), newList("user:5", dashboardGroup, dashboardResource))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, res.GetItems(), 0)
|
assert.Len(t, res.GetItems(), 0)
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||||
"github.com/grafana/grafana/pkg/services/apiserver/options"
|
"github.com/grafana/grafana/pkg/services/apiserver/options"
|
||||||
"github.com/grafana/grafana/pkg/services/authn/grpcutils"
|
"github.com/grafana/grafana/pkg/services/authn/grpcutils"
|
||||||
|
"github.com/grafana/grafana/pkg/services/authz"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||||
|
@ -32,6 +33,7 @@ func ProvideUnifiedStorageClient(
|
||||||
db infraDB.DB,
|
db infraDB.DB,
|
||||||
tracer tracing.Tracer,
|
tracer tracing.Tracer,
|
||||||
reg prometheus.Registerer,
|
reg prometheus.Registerer,
|
||||||
|
authzc authz.Client,
|
||||||
) (resource.ResourceClient, error) {
|
) (resource.ResourceClient, error) {
|
||||||
// See: apiserver.ApplyGrafanaConfig(cfg, features, o)
|
// See: apiserver.ApplyGrafanaConfig(cfg, features, o)
|
||||||
apiserverCfg := cfg.SectionWithEnvOverrides("grafana-apiserver")
|
apiserverCfg := cfg.SectionWithEnvOverrides("grafana-apiserver")
|
||||||
|
@ -95,7 +97,7 @@ func ProvideUnifiedStorageClient(
|
||||||
|
|
||||||
// Use the local SQL
|
// Use the local SQL
|
||||||
default:
|
default:
|
||||||
server, err := sql.NewResourceServer(ctx, db, cfg, features, tracer, reg)
|
server, err := sql.NewResourceServer(ctx, db, cfg, features, tracer, reg, authzc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/authlib/authz"
|
"github.com/grafana/authlib/authz"
|
||||||
"github.com/grafana/authlib/claims"
|
"github.com/grafana/authlib/claims"
|
||||||
|
@ -24,3 +26,82 @@ func (c *staticAuthzClient) Compile(ctx context.Context, id claims.AuthInfo, req
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ authz.AccessClient = &staticAuthzClient{}
|
var _ authz.AccessClient = &staticAuthzClient{}
|
||||||
|
|
||||||
|
type groupResource map[string]map[string]interface{}
|
||||||
|
|
||||||
|
// authzLimitedClient is a client that enforces RBAC for the limited number of groups and resources.
|
||||||
|
// This is a temporary solution until the authz service is fully implemented.
|
||||||
|
// The authz service will be responsible for enforcing RBAC.
|
||||||
|
// For now, it makes one call to the authz service for each list items. This is known to be inefficient.
|
||||||
|
type authzLimitedClient struct {
|
||||||
|
client authz.AccessChecker
|
||||||
|
// whitelist is a map of group to resources that are compatible with RBAC.
|
||||||
|
whitelist groupResource
|
||||||
|
logger *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAuthzLimitedClient creates a new authzLimitedClient.
|
||||||
|
func NewAuthzLimitedClient(client authz.AccessChecker) authz.AccessClient {
|
||||||
|
logger := slog.Default().With("logger", "limited-authz-client")
|
||||||
|
return &authzLimitedClient{
|
||||||
|
client: client,
|
||||||
|
whitelist: groupResource{
|
||||||
|
"dashboard.grafana.app": map[string]interface{}{"dashboards": nil},
|
||||||
|
"folder.grafana.app": map[string]interface{}{"folders": nil},
|
||||||
|
},
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check implements authz.AccessClient.
|
||||||
|
func (c authzLimitedClient) Check(ctx context.Context, id claims.AuthInfo, req authz.CheckRequest) (authz.CheckResponse, error) {
|
||||||
|
if !c.IsCompatibleWithRBAC(req.Group, req.Resource) {
|
||||||
|
c.logger.Debug("Check", "group", req.Group, "resource", req.Resource, "rbac", false, "allowed", true)
|
||||||
|
return authz.CheckResponse{Allowed: true}, nil
|
||||||
|
}
|
||||||
|
t := time.Now()
|
||||||
|
resp, err := c.client.Check(ctx, id, req)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("Check", "group", req.Group, "resource", req.Resource, "rbac", true, "error", err, "duration", time.Since(t))
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
c.logger.Debug("Check", "group", req.Group, "resource", req.Resource, "rbac", true, "allowed", resp.Allowed, "duration", time.Since(t))
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile implements authz.AccessClient.
|
||||||
|
func (c authzLimitedClient) Compile(ctx context.Context, id claims.AuthInfo, req authz.ListRequest) (authz.ItemChecker, error) {
|
||||||
|
return func(namespace string, name, folder string) bool {
|
||||||
|
// TODO: Implement For now we perform the check for each item.
|
||||||
|
if !c.IsCompatibleWithRBAC(req.Group, req.Resource) {
|
||||||
|
c.logger.Debug("Compile.Check", "group", req.Group, "resource", req.Resource, "namespace", namespace, "name", name, "folder", folder, "rbac", false, "allowed", true)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
t := time.Now()
|
||||||
|
r, err := c.client.Check(ctx, id, authz.CheckRequest{
|
||||||
|
Verb: "get",
|
||||||
|
Group: req.Group,
|
||||||
|
Resource: req.Resource,
|
||||||
|
Namespace: namespace,
|
||||||
|
Name: name,
|
||||||
|
Folder: folder,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("Compile.Check", "group", req.Group, "resource", req.Resource, "namespace", namespace, "name", name, "folder", folder, "rbac", true, "error", err, "duration", time.Since(t))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c.logger.Debug("Compile.Check", "group", req.Group, "resource", req.Resource, "namespace", namespace, "name", name, "folder", folder, "rbac", true, "allowed", r.Allowed, "duration", time.Since(t))
|
||||||
|
return r.Allowed
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c authzLimitedClient) IsCompatibleWithRBAC(group, resource string) bool {
|
||||||
|
if _, ok := c.whitelist[group]; ok {
|
||||||
|
if _, ok := c.whitelist[group][resource]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ authz.AccessClient = &authzLimitedClient{}
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
package resource
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/grafana/authlib/authz"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAuthzLimitedClient_Check(t *testing.T) {
|
||||||
|
mockClient := &staticAuthzClient{allowed: false}
|
||||||
|
client := NewAuthzLimitedClient(mockClient)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
group string
|
||||||
|
resource string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"dashboard.grafana.app", "dashboards", false},
|
||||||
|
{"folder.grafana.app", "folders", false},
|
||||||
|
{"unknown.group", "unknown.resource", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
req := authz.CheckRequest{
|
||||||
|
Group: test.group,
|
||||||
|
Resource: test.resource,
|
||||||
|
}
|
||||||
|
resp, err := client.Check(context.Background(), nil, req)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, test.expected, resp.Allowed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthzLimitedClient_Compile(t *testing.T) {
|
||||||
|
mockClient := &staticAuthzClient{allowed: false}
|
||||||
|
client := NewAuthzLimitedClient(mockClient)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
group string
|
||||||
|
resource string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"dashboard.grafana.app", "dashboards", false},
|
||||||
|
{"folder.grafana.app", "folders", false},
|
||||||
|
{"unknown.group", "unknown.resource", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
req := authz.ListRequest{
|
||||||
|
Group: test.group,
|
||||||
|
Resource: test.resource,
|
||||||
|
}
|
||||||
|
checker, err := client.Compile(context.Background(), nil, req)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, checker)
|
||||||
|
|
||||||
|
result := checker("namespace", "name", "folder")
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
infraDB "github.com/grafana/grafana/pkg/infra/db"
|
infraDB "github.com/grafana/grafana/pkg/infra/db"
|
||||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||||
|
"github.com/grafana/grafana/pkg/services/authz"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||||
|
@ -18,7 +19,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Creates a new ResourceServer
|
// Creates a new ResourceServer
|
||||||
func NewResourceServer(ctx context.Context, db infraDB.DB, cfg *setting.Cfg, features featuremgmt.FeatureToggles, tracer tracing.Tracer, reg prometheus.Registerer) (resource.ResourceServer, error) {
|
func NewResourceServer(ctx context.Context, db infraDB.DB, cfg *setting.Cfg, features featuremgmt.FeatureToggles, tracer tracing.Tracer, reg prometheus.Registerer, ac authz.Client) (resource.ResourceServer, error) {
|
||||||
apiserverCfg := cfg.SectionWithEnvOverrides("grafana-apiserver")
|
apiserverCfg := cfg.SectionWithEnvOverrides("grafana-apiserver")
|
||||||
opts := resource.ResourceServerOptions{
|
opts := resource.ResourceServerOptions{
|
||||||
Tracer: tracer,
|
Tracer: tracer,
|
||||||
|
@ -27,7 +28,9 @@ func NewResourceServer(ctx context.Context, db infraDB.DB, cfg *setting.Cfg, fea
|
||||||
},
|
},
|
||||||
Reg: reg,
|
Reg: reg,
|
||||||
}
|
}
|
||||||
|
if ac != nil {
|
||||||
|
opts.AccessClient = resource.NewAuthzLimitedClient(ac)
|
||||||
|
}
|
||||||
// Support local file blob
|
// Support local file blob
|
||||||
if strings.HasPrefix(opts.Blob.URL, "./data/") {
|
if strings.HasPrefix(opts.Blob.URL, "./data/") {
|
||||||
dir := strings.Replace(opts.Blob.URL, "./data", cfg.DataPath, 1)
|
dir := strings.Replace(opts.Blob.URL, "./data", cfg.DataPath, 1)
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||||
"github.com/grafana/grafana/pkg/modules"
|
"github.com/grafana/grafana/pkg/modules"
|
||||||
"github.com/grafana/grafana/pkg/services/authn/grpcutils"
|
"github.com/grafana/grafana/pkg/services/authn/grpcutils"
|
||||||
|
"github.com/grafana/grafana/pkg/services/authz"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/grpcserver"
|
"github.com/grafana/grafana/pkg/services/grpcserver"
|
||||||
"github.com/grafana/grafana/pkg/services/grpcserver/interceptors"
|
"github.com/grafana/grafana/pkg/services/grpcserver/interceptors"
|
||||||
|
@ -93,7 +94,12 @@ func ProvideUnifiedStorageGrpcService(
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) start(ctx context.Context) error {
|
func (s *service) start(ctx context.Context) error {
|
||||||
server, err := NewResourceServer(ctx, s.db, s.cfg, s.features, s.tracing, s.reg)
|
authzClient, err := authz.ProvideStandaloneAuthZClient(s.cfg, s.features, s.tracing)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
server, err := NewResourceServer(ctx, s.db, s.cfg, s.features, s.tracing, s.reg, authzClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ interface DashboardWithAccessInfo extends Resource<DashboardDataDTO, 'DashboardW
|
||||||
access: Object; // TODO...
|
access: Object; // TODO...
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implemented using /apis/dashboards.grafana.app/*
|
// Implemented using /apis/dashboard.grafana.app/*
|
||||||
class K8sDashboardAPI implements DashboardAPI {
|
class K8sDashboardAPI implements DashboardAPI {
|
||||||
private client: ResourceClient<DashboardDataDTO>;
|
private client: ResourceClient<DashboardDataDTO>;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue