mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Zanzana: Setup GRPC authentication in client/server mode (#98680)
* Zanzana: Setup GRPC authentication in client/server mode * don't use grpcutils * refactor Co-authored-by: Karl Persson <kalle.persson@grafana.com> * Add a namespace stub for in-proc mode Co-authored-by: Karl Persson <kalle.persson@grafana.com> * Read parameters from config * authorize server requests * add namespace to the tests context * use stack id from config * simplify authorize func * properly format namespace * return Unauthenticated if namespace is empty * use insecure cred only in dev env * check request namespace * Use CallCredentials API for client auth * provide config * fail if stack id is missing * improve error message * use insecure connection by default --------- Co-authored-by: Karl Persson <kalle.persson@grafana.com>
This commit is contained in:
parent
8f79a59e1f
commit
5922015fec
@ -6,12 +6,16 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fullstorydev/grpchan/inprocgrpc"
|
||||
authnlib "github.com/grafana/authlib/authn"
|
||||
authzv1 "github.com/grafana/authlib/authz/proto/v1"
|
||||
"github.com/grafana/authlib/claims"
|
||||
"github.com/grafana/dskit/services"
|
||||
grpcAuth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
@ -22,9 +26,12 @@ import (
|
||||
zserver "github.com/grafana/grafana/pkg/services/authz/zanzana/server"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/grpcserver"
|
||||
"github.com/grafana/grafana/pkg/services/grpcserver/interceptors"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
const zanzanaAudience = "zanzana"
|
||||
|
||||
// ProvideZanzana used to register ZanzanaClient.
|
||||
// It will also start an embedded ZanzanaSever if mode is set to "embedded".
|
||||
func ProvideZanzana(cfg *setting.Cfg, db db.DB, features featuremgmt.FeatureToggles) (zanzana.Client, error) {
|
||||
@ -37,7 +44,32 @@ func ProvideZanzana(cfg *setting.Cfg, db db.DB, features featuremgmt.FeatureTogg
|
||||
var client zanzana.Client
|
||||
switch cfg.Zanzana.Mode {
|
||||
case setting.ZanzanaModeClient:
|
||||
conn, err := grpc.NewClient(cfg.Zanzana.Addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
tokenClient, err := authnlib.NewTokenExchangeClient(authnlib.TokenExchangeConfig{
|
||||
Token: cfg.Zanzana.Token,
|
||||
TokenExchangeURL: cfg.Zanzana.TokenExchangeURL,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize token exchange client: %w", err)
|
||||
}
|
||||
|
||||
if cfg.StackID == "" {
|
||||
return nil, fmt.Errorf("missing stack ID")
|
||||
}
|
||||
namespace := fmt.Sprintf("stacks-%s", cfg.StackID)
|
||||
|
||||
tokenAuthCred := &tokenAuth{
|
||||
cfg: cfg,
|
||||
namespace: namespace,
|
||||
tokenClient: tokenClient,
|
||||
}
|
||||
|
||||
dialOptions := []grpc.DialOption{
|
||||
// TODO: add TLS support
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithPerRPCCredentials(tokenAuthCred),
|
||||
}
|
||||
|
||||
conn, err := grpc.NewClient(cfg.Zanzana.Addr, dialOptions...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create zanzana client to remote server: %w", err)
|
||||
}
|
||||
@ -61,7 +93,18 @@ func ProvideZanzana(cfg *setting.Cfg, db db.DB, features featuremgmt.FeatureTogg
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to start zanzana: %w", err)
|
||||
}
|
||||
|
||||
channel := &inprocgrpc.Channel{}
|
||||
// Put * as a namespace so we can properly authorize request with in-proc mode
|
||||
channel.WithServerUnaryInterceptor(grpcAuth.UnaryServerInterceptor(func(ctx context.Context) (context.Context, error) {
|
||||
ctx = claims.WithClaims(ctx, authnlib.NewAccessTokenAuthInfo(authnlib.Claims[authnlib.AccessTokenClaims]{
|
||||
Rest: authnlib.AccessTokenClaims{
|
||||
Namespace: "*",
|
||||
},
|
||||
}))
|
||||
return ctx, nil
|
||||
}))
|
||||
|
||||
openfgav1.RegisterOpenFGAServiceServer(channel, openfga)
|
||||
authzv1.RegisterAuthzServiceServer(channel, srv)
|
||||
authzextv1.RegisterAuthzExtentionServiceServer(channel, srv)
|
||||
@ -134,9 +177,30 @@ func (z *Zanzana) start(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// FIXME(kalleep): For now we use noopAuthenticator but we should create an authenticator that can be shared
|
||||
// between different services.
|
||||
z.handle, err = grpcserver.ProvideService(z.cfg, z.features, noopAuthenticator{}, tracer, prometheus.DefaultRegisterer)
|
||||
authenticator := authnlib.NewAccessTokenAuthenticator(
|
||||
authnlib.NewAccessTokenVerifier(
|
||||
authnlib.VerifierConfig{
|
||||
AllowedAudiences: []string{zanzanaAudience},
|
||||
},
|
||||
authnlib.NewKeyRetriever(authnlib.KeyRetrieverConfig{
|
||||
SigningKeysURL: z.cfg.Zanzana.SigningKeysURL,
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
authfn := interceptors.AuthenticatorFunc(func(ctx context.Context) (context.Context, error) {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing metadata")
|
||||
}
|
||||
c, err := authenticator.Authenticate(ctx, authnlib.NewGRPCTokenProvider(md))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return claims.WithClaims(ctx, c), nil
|
||||
})
|
||||
|
||||
z.handle, err = grpcserver.ProvideService(z.cfg, z.features, authfn, tracer, prometheus.DefaultRegisterer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create zanzana grpc server: %w", err)
|
||||
}
|
||||
@ -175,8 +239,26 @@ func (z *Zanzana) stopping(err error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type noopAuthenticator struct{}
|
||||
|
||||
func (n noopAuthenticator) Authenticate(ctx context.Context) (context.Context, error) {
|
||||
return ctx, nil
|
||||
type tokenAuth struct {
|
||||
cfg *setting.Cfg
|
||||
namespace string
|
||||
tokenClient *authnlib.TokenExchangeClient
|
||||
}
|
||||
|
||||
func (t *tokenAuth) GetRequestMetadata(ctx context.Context, _ ...string) (map[string]string, error) {
|
||||
token, err := t.tokenClient.Exchange(ctx, authnlib.TokenExchangeRequest{
|
||||
Namespace: t.namespace,
|
||||
Audiences: []string{zanzanaAudience},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return map[string]string{
|
||||
authnlib.DefaultAccessTokenMetadataKey: token.Token,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *tokenAuth) RequireTransportSecurity() bool {
|
||||
return false
|
||||
}
|
||||
|
23
pkg/services/authz/zanzana/server/auth.go
Normal file
23
pkg/services/authz/zanzana/server/auth.go
Normal file
@ -0,0 +1,23 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/authlib/claims"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func authorize(ctx context.Context, namespace string) error {
|
||||
c, ok := claims.From(ctx)
|
||||
if !ok {
|
||||
return status.Errorf(codes.Unauthenticated, "unauthenticated")
|
||||
}
|
||||
if c.GetNamespace() == "" || namespace == "" {
|
||||
return status.Errorf(codes.Unauthenticated, "unauthenticated")
|
||||
}
|
||||
if !claims.NamespaceMatches(c.GetNamespace(), namespace) {
|
||||
return status.Errorf(codes.PermissionDenied, "namespace does not match")
|
||||
}
|
||||
return nil
|
||||
}
|
@ -13,6 +13,10 @@ func (s *Server) BatchCheck(ctx context.Context, r *authzextv1.BatchCheckRequest
|
||||
ctx, span := tracer.Start(ctx, "authzServer.BatchCheck")
|
||||
defer span.End()
|
||||
|
||||
if err := authorize(ctx, r.GetNamespace()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
batchRes := &authzextv1.BatchCheckResponse{
|
||||
Groups: make(map[string]*authzextv1.BatchCheckGroupResource),
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -34,7 +33,7 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||
|
||||
t.Run("user:1 should only be able to read resource:dashboard.grafana.app/dashboards/1", func(t *testing.T) {
|
||||
groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, "")
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:1", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
|
||||
res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:1", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
|
||||
{Name: "1", Folder: "1"},
|
||||
{Name: "2", Folder: "2"},
|
||||
}))
|
||||
@ -47,7 +46,7 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||
|
||||
t.Run("user:2 should be able to read resource:dashboard.grafana.app/dashboards/{1,2} through group_resource", func(t *testing.T) {
|
||||
groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, "")
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:2", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
|
||||
res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:2", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
|
||||
{Name: "1", Folder: "1"},
|
||||
{Name: "2", Folder: "2"},
|
||||
}))
|
||||
@ -57,7 +56,7 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||
|
||||
t.Run("user:3 should be able to read resource:dashboard.grafana.app/dashboards/1 with set relation", func(t *testing.T) {
|
||||
groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, "")
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:3", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
|
||||
res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:3", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
|
||||
{Name: "1", Folder: "1"},
|
||||
{Name: "2", Folder: "2"},
|
||||
}))
|
||||
@ -70,7 +69,7 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||
|
||||
t.Run("user:4 should be able to read all dashboard.grafana.app/dashboards in folder 1 and 3", func(t *testing.T) {
|
||||
groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, "")
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
|
||||
res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
|
||||
{Name: "1", Folder: "1"},
|
||||
{Name: "2", Folder: "3"},
|
||||
{Name: "3", Folder: "2"},
|
||||
@ -85,7 +84,7 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||
|
||||
t.Run("user:5 should be able to read resource:dashboard.grafana.app/dashboards/1 through folder with set relation", func(t *testing.T) {
|
||||
groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, "")
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:5", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
|
||||
res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:5", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
|
||||
{Name: "1", Folder: "1"},
|
||||
{Name: "2", Folder: "2"},
|
||||
}))
|
||||
@ -98,7 +97,7 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||
|
||||
t.Run("user:6 should be able to read folder 1", func(t *testing.T) {
|
||||
groupResource := common.FormatGroupResource(folderGroup, folderResource, "")
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:6", utils.VerbGet, folderGroup, folderResource, "", []*authzextv1.BatchCheckItem{
|
||||
res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:6", utils.VerbGet, folderGroup, folderResource, "", []*authzextv1.BatchCheckItem{
|
||||
{Name: "1"},
|
||||
{Name: "2"},
|
||||
}))
|
||||
@ -111,7 +110,7 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||
|
||||
t.Run("user:7 should be able to read folder {1,2} through group_resource access", func(t *testing.T) {
|
||||
groupResource := common.FormatGroupResource(folderGroup, folderResource, "")
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:7", utils.VerbGet, folderGroup, folderResource, "", []*authzextv1.BatchCheckItem{
|
||||
res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:7", utils.VerbGet, folderGroup, folderResource, "", []*authzextv1.BatchCheckItem{
|
||||
{Name: "1"},
|
||||
{Name: "2"},
|
||||
}))
|
||||
@ -123,7 +122,7 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||
|
||||
t.Run("user:8 should be able to read all resoruce:dashboard.grafana.app/dashboards in folder 6 through folder 5", func(t *testing.T) {
|
||||
groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, "")
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:8", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
|
||||
res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:8", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
|
||||
{Name: "10", Folder: "6"},
|
||||
{Name: "20", Folder: "6"},
|
||||
}))
|
||||
@ -135,7 +134,7 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||
|
||||
t.Run("user:9 should be able to create dashboards in folder 6 through folder 5", func(t *testing.T) {
|
||||
groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, "")
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:9", utils.VerbCreate, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
|
||||
res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:9", utils.VerbCreate, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
|
||||
{Name: "10", Folder: "6"},
|
||||
{Name: "20", Folder: "6"},
|
||||
}))
|
||||
@ -148,7 +147,7 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||
|
||||
t.Run("user:10 should be able to get dashboard status for 10 and 11", func(t *testing.T) {
|
||||
groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, statusSubresource)
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:10", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, []*authzextv1.BatchCheckItem{
|
||||
res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:10", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, []*authzextv1.BatchCheckItem{
|
||||
{Name: "10", Folder: "6"},
|
||||
{Name: "11", Folder: "6"},
|
||||
{Name: "12", Folder: "6"},
|
||||
@ -163,7 +162,7 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||
|
||||
t.Run("user:11 should be able to get dashboard status for 10, 11 and 12 through group_resource", func(t *testing.T) {
|
||||
groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, statusSubresource)
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:11", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, []*authzextv1.BatchCheckItem{
|
||||
res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:11", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, []*authzextv1.BatchCheckItem{
|
||||
{Name: "10", Folder: "6"},
|
||||
{Name: "11", Folder: "6"},
|
||||
{Name: "12", Folder: "6"},
|
||||
@ -178,7 +177,7 @@ func testBatchCheck(t *testing.T, server *Server) {
|
||||
|
||||
t.Run("user:12 should be able to get dashboard status in folder 5 and 6", func(t *testing.T) {
|
||||
groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, statusSubresource)
|
||||
res, err := server.BatchCheck(context.Background(), newReq("user:12", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, []*authzextv1.BatchCheckItem{
|
||||
res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:12", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, []*authzextv1.BatchCheckItem{
|
||||
{Name: "10", Folder: "5"},
|
||||
{Name: "11", Folder: "6"},
|
||||
{Name: "12", Folder: "6"},
|
||||
|
@ -15,6 +15,10 @@ func (s *Server) Check(ctx context.Context, r *authzv1.CheckRequest) (*authzv1.C
|
||||
ctx, span := tracer.Start(ctx, "authzServer.Check")
|
||||
defer span.End()
|
||||
|
||||
if err := authorize(ctx, r.GetNamespace()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
store, err := s.getStoreInfo(ctx, r.GetNamespace())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -1,7 +1,6 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
authzv1 "github.com/grafana/authlib/authz/proto/v1"
|
||||
@ -26,130 +25,130 @@ func testCheck(t *testing.T, server *Server) {
|
||||
}
|
||||
|
||||
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(), newReq("user:1", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "1"))
|
||||
res, err := server.Check(newContextWithNamespace(), newReq("user:1", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
// sanity check
|
||||
res, err = server.Check(context.Background(), newReq("user:1", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "2"))
|
||||
res, err = server.Check(newContextWithNamespace(), newReq("user:1", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "2"))
|
||||
require.NoError(t, err)
|
||||
assert.False(t, res.GetAllowed())
|
||||
|
||||
// sanity check no access to subresource
|
||||
res, err = server.Check(context.Background(), newReq("user:1", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "1", "1"))
|
||||
res, err = server.Check(newContextWithNamespace(), newReq("user:1", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "1", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.False(t, res.GetAllowed())
|
||||
})
|
||||
|
||||
t.Run("user:2 should be able to read resource:dashboard.grafana.app/dashboards/1 through group_resource", func(t *testing.T) {
|
||||
res, err := server.Check(context.Background(), newReq("user:2", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "1"))
|
||||
res, err := server.Check(newContextWithNamespace(), newReq("user:2", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
})
|
||||
|
||||
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(), newReq("user:3", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "1"))
|
||||
res, err := server.Check(newContextWithNamespace(), newReq("user:3", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
// sanity check
|
||||
res, err = server.Check(context.Background(), newReq("user:3", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "2"))
|
||||
res, err = server.Check(newContextWithNamespace(), newReq("user:3", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "2"))
|
||||
require.NoError(t, err)
|
||||
assert.False(t, res.GetAllowed())
|
||||
})
|
||||
|
||||
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(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "1"))
|
||||
res, err := server.Check(newContextWithNamespace(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
res, err = server.Check(context.Background(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "", "3", "2"))
|
||||
res, err = server.Check(newContextWithNamespace(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "", "3", "2"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
// sanity check
|
||||
res, err = server.Check(context.Background(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "2"))
|
||||
res, err = server.Check(newContextWithNamespace(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "2"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
res, err = server.Check(context.Background(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "", "2", "2"))
|
||||
res, err = server.Check(newContextWithNamespace(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "", "2", "2"))
|
||||
require.NoError(t, err)
|
||||
assert.False(t, res.GetAllowed())
|
||||
})
|
||||
|
||||
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(), newReq("user:5", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "1"))
|
||||
res, err := server.Check(newContextWithNamespace(), newReq("user:5", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
})
|
||||
|
||||
t.Run("user:6 should be able to read folder 1 ", func(t *testing.T) {
|
||||
res, err := server.Check(context.Background(), newReq("user:6", utils.VerbGet, folderGroup, folderResource, "", "", "1"))
|
||||
res, err := server.Check(newContextWithNamespace(), newReq("user:6", utils.VerbGet, folderGroup, folderResource, "", "", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
})
|
||||
|
||||
t.Run("user:7 should be able to read folder one through group_resource access", func(t *testing.T) {
|
||||
res, err := server.Check(context.Background(), newReq("user:7", utils.VerbGet, folderGroup, folderResource, "", "", "1"))
|
||||
res, err := server.Check(newContextWithNamespace(), newReq("user:7", utils.VerbGet, folderGroup, folderResource, "", "", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
res, err = server.Check(context.Background(), newReq("user:7", utils.VerbGet, folderGroup, folderResource, "", "", "10"))
|
||||
res, err = server.Check(newContextWithNamespace(), newReq("user:7", utils.VerbGet, folderGroup, folderResource, "", "", "10"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
})
|
||||
|
||||
t.Run("user:8 should be able to read all resoruce:dashboard.grafana.app/dashboar in folder 6 through folder 5", func(t *testing.T) {
|
||||
res, err := server.Check(context.Background(), newReq("user:8", utils.VerbGet, dashboardGroup, dashboardResource, "", "6", "10"))
|
||||
res, err := server.Check(newContextWithNamespace(), newReq("user:8", utils.VerbGet, dashboardGroup, dashboardResource, "", "6", "10"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
res, err = server.Check(context.Background(), newReq("user:8", utils.VerbGet, dashboardGroup, dashboardResource, "", "5", "11"))
|
||||
res, err = server.Check(newContextWithNamespace(), newReq("user:8", utils.VerbGet, dashboardGroup, dashboardResource, "", "5", "11"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
res, err = server.Check(context.Background(), newReq("user:8", utils.VerbGet, folderGroup, folderResource, "", "4", "12"))
|
||||
res, err = server.Check(newContextWithNamespace(), newReq("user:8", utils.VerbGet, folderGroup, folderResource, "", "4", "12"))
|
||||
require.NoError(t, err)
|
||||
assert.False(t, res.GetAllowed())
|
||||
})
|
||||
|
||||
t.Run("user:9 should be able to create dashboards in folder 5", func(t *testing.T) {
|
||||
res, err := server.Check(context.Background(), newReq("user:9", utils.VerbCreate, dashboardGroup, dashboardResource, "", "5", ""))
|
||||
res, err := server.Check(newContextWithNamespace(), newReq("user:9", utils.VerbCreate, dashboardGroup, dashboardResource, "", "5", ""))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
})
|
||||
|
||||
t.Run("user:10 should be able to read dashboard status for dashboard 10", func(t *testing.T) {
|
||||
res, err := server.Check(context.Background(), newReq("user:10", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "", "10"))
|
||||
res, err := server.Check(newContextWithNamespace(), newReq("user:10", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "", "10"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
res, err = server.Check(context.Background(), newReq("user:10", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "", "1"))
|
||||
res, err = server.Check(newContextWithNamespace(), newReq("user:10", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "", "1"))
|
||||
require.NoError(t, err)
|
||||
assert.False(t, res.GetAllowed())
|
||||
})
|
||||
|
||||
t.Run("user:11 should be able to read dashboard status for dashboard 10 through group_resource", func(t *testing.T) {
|
||||
res, err := server.Check(context.Background(), newReq("user:11", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "", "10"))
|
||||
res, err := server.Check(newContextWithNamespace(), newReq("user:11", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "", "10"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
})
|
||||
|
||||
t.Run("user:12 should be able to read dashboard status for all dashboards in folder 5", func(t *testing.T) {
|
||||
res, err := server.Check(context.Background(), newReq("user:12", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "5", "10"))
|
||||
res, err := server.Check(newContextWithNamespace(), newReq("user:12", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "5", "10"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
res, err = server.Check(context.Background(), newReq("user:12", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "5", "11"))
|
||||
res, err = server.Check(newContextWithNamespace(), newReq("user:12", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "5", "11"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
// inherited from folder 5
|
||||
res, err = server.Check(context.Background(), newReq("user:12", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "6", "12"))
|
||||
res, err = server.Check(newContextWithNamespace(), newReq("user:12", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "6", "12"))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAllowed())
|
||||
|
||||
res, err = server.Check(context.Background(), newReq("user:12", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "1", "13"))
|
||||
res, err = server.Check(newContextWithNamespace(), newReq("user:12", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "1", "13"))
|
||||
require.NoError(t, err)
|
||||
assert.False(t, res.GetAllowed())
|
||||
})
|
||||
|
@ -15,6 +15,10 @@ func (s *Server) List(ctx context.Context, r *authzv1.ListRequest) (*authzv1.Lis
|
||||
ctx, span := tracer.Start(ctx, "authzServer.List")
|
||||
defer span.End()
|
||||
|
||||
if err := authorize(ctx, r.GetNamespace()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
store, err := s.getStoreInfo(ctx, r.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -1,13 +1,13 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
authzv1 "github.com/grafana/authlib/authz/proto/v1"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
)
|
||||
|
||||
@ -24,7 +24,7 @@ func testList(t *testing.T, server *Server) {
|
||||
}
|
||||
|
||||
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(newContextWithNamespace(), newList("user:1", dashboardGroup, dashboardResource, ""))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, res.GetItems(), 1)
|
||||
assert.Len(t, res.GetFolders(), 0)
|
||||
@ -32,7 +32,7 @@ func testList(t *testing.T, server *Server) {
|
||||
})
|
||||
|
||||
t.Run("user:2 should be able to list all through group", func(t *testing.T) {
|
||||
res, err := server.List(context.Background(), newList("user:2", dashboardGroup, dashboardResource, ""))
|
||||
res, err := server.List(newContextWithNamespace(), newList("user:2", dashboardGroup, dashboardResource, ""))
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.GetAll())
|
||||
assert.Len(t, res.GetItems(), 0)
|
||||
@ -40,7 +40,7 @@ func testList(t *testing.T, server *Server) {
|
||||
})
|
||||
|
||||
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(newContextWithNamespace(), newList("user:3", dashboardGroup, dashboardResource, ""))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, res.GetItems(), 1)
|
||||
@ -49,7 +49,7 @@ func testList(t *testing.T, server *Server) {
|
||||
})
|
||||
|
||||
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(newContextWithNamespace(), newList("user:4", dashboardGroup, dashboardResource, ""))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, res.GetItems(), 0)
|
||||
assert.Len(t, res.GetFolders(), 2)
|
||||
@ -59,7 +59,7 @@ func testList(t *testing.T, server *Server) {
|
||||
})
|
||||
|
||||
t.Run("user:5 should be 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(newContextWithNamespace(), newList("user:5", dashboardGroup, dashboardResource, ""))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, res.GetItems(), 0)
|
||||
assert.Len(t, res.GetFolders(), 1)
|
||||
@ -67,7 +67,7 @@ func testList(t *testing.T, server *Server) {
|
||||
})
|
||||
|
||||
t.Run("user:6 should be able to list folder 1", func(t *testing.T) {
|
||||
res, err := server.List(context.Background(), newList("user:6", folderGroup, folderResource, ""))
|
||||
res, err := server.List(newContextWithNamespace(), newList("user:6", folderGroup, folderResource, ""))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, res.GetItems(), 1)
|
||||
assert.Len(t, res.GetFolders(), 0)
|
||||
@ -75,7 +75,7 @@ func testList(t *testing.T, server *Server) {
|
||||
})
|
||||
|
||||
t.Run("user:7 should be able to list all folders", func(t *testing.T) {
|
||||
res, err := server.List(context.Background(), newList("user:7", folderGroup, folderResource, ""))
|
||||
res, err := server.List(newContextWithNamespace(), newList("user:7", folderGroup, folderResource, ""))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, res.GetItems(), 0)
|
||||
assert.Len(t, res.GetFolders(), 0)
|
||||
@ -83,7 +83,7 @@ func testList(t *testing.T, server *Server) {
|
||||
})
|
||||
|
||||
t.Run("user:8 should be able to list resoruce:dashboard.grafana.app/dashboard in folder 6 and folder 5", func(t *testing.T) {
|
||||
res, err := server.List(context.Background(), newList("user:8", dashboardGroup, dashboardResource, ""))
|
||||
res, err := server.List(newContextWithNamespace(), newList("user:8", dashboardGroup, dashboardResource, ""))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, res.GetFolders(), 2)
|
||||
|
||||
@ -92,7 +92,7 @@ func testList(t *testing.T, server *Server) {
|
||||
})
|
||||
|
||||
t.Run("user:10 should be able to get resoruce:dashboard.grafana.app/dashboard/status for 10 and 11", func(t *testing.T) {
|
||||
res, err := server.List(context.Background(), newList("user:10", dashboardGroup, dashboardResource, statusSubresource))
|
||||
res, err := server.List(newContextWithNamespace(), newList("user:10", dashboardGroup, dashboardResource, statusSubresource))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, res.GetFolders(), 0)
|
||||
assert.Len(t, res.GetItems(), 2)
|
||||
@ -102,7 +102,7 @@ func testList(t *testing.T, server *Server) {
|
||||
})
|
||||
|
||||
t.Run("user:11 should be able to list all resoruce:dashboard.grafana.app/dashboard/status ", func(t *testing.T) {
|
||||
res, err := server.List(context.Background(), newList("user:11", dashboardGroup, dashboardResource, statusSubresource))
|
||||
res, err := server.List(newContextWithNamespace(), newList("user:11", dashboardGroup, dashboardResource, statusSubresource))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, res.GetItems(), 0)
|
||||
assert.Len(t, res.GetFolders(), 0)
|
||||
@ -110,7 +110,7 @@ func testList(t *testing.T, server *Server) {
|
||||
})
|
||||
|
||||
t.Run("user:12 should be able to list all resoruce:dashboard.grafana.app/dashboard/status in folder 5 and 6", func(t *testing.T) {
|
||||
res, err := server.List(context.Background(), newList("user:12", dashboardGroup, dashboardResource, statusSubresource))
|
||||
res, err := server.List(newContextWithNamespace(), newList("user:12", dashboardGroup, dashboardResource, statusSubresource))
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, res.GetItems(), 0)
|
||||
assert.Len(t, res.GetFolders(), 2)
|
||||
|
@ -13,6 +13,10 @@ func (s *Server) Read(ctx context.Context, req *authzextv1.ReadRequest) (*authze
|
||||
ctx, span := tracer.Start(ctx, "authzServer.Read")
|
||||
defer span.End()
|
||||
|
||||
if err := authorize(ctx, req.GetNamespace()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storeInf, err := s.getStoreInfo(ctx, req.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
authnlib "github.com/grafana/authlib/authn"
|
||||
"github.com/grafana/authlib/claims"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@ -102,3 +104,13 @@ func setup(t *testing.T, testDB db.DB, cfg *setting.Cfg) *Server {
|
||||
require.NoError(t, err)
|
||||
return srv
|
||||
}
|
||||
|
||||
func newContextWithNamespace() context.Context {
|
||||
ctx := context.Background()
|
||||
ctx = claims.WithClaims(ctx, authnlib.NewAccessTokenAuthInfo(authnlib.Claims[authnlib.AccessTokenClaims]{
|
||||
Rest: authnlib.AccessTokenClaims{
|
||||
Namespace: "*",
|
||||
},
|
||||
}))
|
||||
return ctx
|
||||
}
|
||||
|
@ -13,6 +13,10 @@ func (s *Server) Write(ctx context.Context, req *authzextv1.WriteRequest) (*auth
|
||||
ctx, span := tracer.Start(ctx, "authzServer.Write")
|
||||
defer span.End()
|
||||
|
||||
if err := authorize(ctx, req.GetNamespace()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storeInf, err := s.getStoreInfo(ctx, req.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -21,6 +21,12 @@ type Authenticator interface {
|
||||
Authenticate(ctx context.Context) (context.Context, error)
|
||||
}
|
||||
|
||||
type AuthenticatorFunc func(context.Context) (context.Context, error)
|
||||
|
||||
func (fn AuthenticatorFunc) Authenticate(ctx context.Context) (context.Context, error) {
|
||||
return fn(ctx)
|
||||
}
|
||||
|
||||
// authenticator can authenticate GRPC requests.
|
||||
type authenticator struct {
|
||||
contextHandler grpccontext.ContextHandler
|
||||
|
@ -37,6 +37,13 @@ type ZanzanaSettings struct {
|
||||
// Use streamed version of list objects.
|
||||
// Returns full list of objects, but takes more time.
|
||||
UseStreamedListObjects bool
|
||||
|
||||
// Token used to perform the exchange request.
|
||||
Token string
|
||||
// URL called to perform exchange request.
|
||||
TokenExchangeURL string
|
||||
// URL for signing keys
|
||||
SigningKeysURL string
|
||||
}
|
||||
|
||||
func (cfg *Cfg) readZanzanaSettings() {
|
||||
@ -63,5 +70,9 @@ func (cfg *Cfg) readZanzanaSettings() {
|
||||
s.ListObjectsMaxResults = uint32(sec.Key("list_objects_max_results").MustUint(1000))
|
||||
s.UseStreamedListObjects = sec.Key("use_streamed_list_objects").MustBool(false)
|
||||
|
||||
s.Token = sec.Key("token").MustString("")
|
||||
s.TokenExchangeURL = sec.Key("token_exchange_url").MustString("")
|
||||
s.SigningKeysURL = sec.Key("signing_keys_url").MustString("")
|
||||
|
||||
cfg.Zanzana = s
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user