Zanzana: Split up settings into client and server sections (#99066)

* Split up zanzana settings into client and server sections

* Update workspace
This commit is contained in:
Karl Persson
2025-01-16 13:39:39 +01:00
committed by GitHub
parent ee4016f4bc
commit 2187a66f2b
10 changed files with 150 additions and 157 deletions
+25 -23
View File
@@ -22,8 +22,6 @@ import (
"github.com/grafana/grafana/pkg/infra/tracing"
authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
"github.com/grafana/grafana/pkg/services/authz/zanzana"
zclient "github.com/grafana/grafana/pkg/services/authz/zanzana/client"
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"
@@ -36,17 +34,17 @@ const zanzanaAudience = "zanzana"
// 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) {
if !features.IsEnabledGlobally(featuremgmt.FlagZanzana) {
return zclient.NewNoop(), nil
return zanzana.NewNoopClient(), nil
}
logger := log.New("zanzana")
var client zanzana.Client
switch cfg.Zanzana.Mode {
switch cfg.ZanzanaClient.Mode {
case setting.ZanzanaModeClient:
tokenClient, err := authnlib.NewTokenExchangeClient(authnlib.TokenExchangeConfig{
Token: cfg.Zanzana.Token,
TokenExchangeURL: cfg.Zanzana.TokenExchangeURL,
Token: cfg.ZanzanaClient.Token,
TokenExchangeURL: cfg.ZanzanaClient.TokenExchangeURL,
})
if err != nil {
return nil, fmt.Errorf("failed to initialize token exchange client: %w", err)
@@ -69,12 +67,12 @@ func ProvideZanzana(cfg *setting.Cfg, db db.DB, features featuremgmt.FeatureTogg
grpc.WithPerRPCCredentials(tokenAuthCred),
}
conn, err := grpc.NewClient(cfg.Zanzana.Addr, dialOptions...)
conn, err := grpc.NewClient(cfg.ZanzanaClient.Addr, dialOptions...)
if err != nil {
return nil, fmt.Errorf("failed to create zanzana client to remote server: %w", err)
}
client, err = zclient.NewClient(context.Background(), conn, cfg)
client, err = zanzana.NewClient(conn)
if err != nil {
return nil, fmt.Errorf("failed to initialize zanzana client: %w", err)
}
@@ -84,12 +82,12 @@ func ProvideZanzana(cfg *setting.Cfg, db db.DB, features featuremgmt.FeatureTogg
return nil, fmt.Errorf("failed to start zanzana: %w", err)
}
openfga, err := zserver.NewOpenFGA(&cfg.Zanzana, store, logger)
openfga, err := zanzana.NewOpenFGAServer(cfg.ZanzanaServer, store, logger)
if err != nil {
return nil, fmt.Errorf("failed to start zanzana: %w", err)
}
srv, err := zserver.NewAuthzServer(cfg, openfga)
srv, err := zanzana.NewServer(cfg.ZanzanaServer, openfga, logger)
if err != nil {
return nil, fmt.Errorf("failed to start zanzana: %w", err)
}
@@ -109,13 +107,13 @@ func ProvideZanzana(cfg *setting.Cfg, db db.DB, features featuremgmt.FeatureTogg
authzv1.RegisterAuthzServiceServer(channel, srv)
authzextv1.RegisterAuthzExtentionServiceServer(channel, srv)
client, err = zclient.NewClient(context.Background(), channel, cfg)
client, err = zanzana.NewClient(channel)
if err != nil {
return nil, fmt.Errorf("failed to initialize zanzana client: %w", err)
}
default:
return nil, fmt.Errorf("unsupported zanzana mode: %s", cfg.Zanzana.Mode)
return nil, fmt.Errorf("unsupported zanzana mode: %s", cfg.ZanzanaClient.Mode)
}
return client, nil
@@ -156,12 +154,12 @@ func (z *Zanzana) start(ctx context.Context) error {
return fmt.Errorf("failed to initilize zanana store: %w", err)
}
openfga, err := zserver.NewOpenFGA(&z.cfg.Zanzana, store, z.logger)
openfga, err := zanzana.NewOpenFGAServer(z.cfg.ZanzanaServer, store, z.logger)
if err != nil {
return fmt.Errorf("failed to start zanzana: %w", err)
}
srv, err := zserver.NewAuthzServer(z.cfg, openfga)
srv, err := zanzana.NewServer(z.cfg.ZanzanaServer, openfga, z.logger)
if err != nil {
return fmt.Errorf("failed to start zanzana: %w", err)
}
@@ -183,7 +181,7 @@ func (z *Zanzana) start(ctx context.Context) error {
AllowedAudiences: []string{zanzanaAudience},
},
authnlib.NewKeyRetriever(authnlib.KeyRetrieverConfig{
SigningKeysURL: z.cfg.Zanzana.SigningKeysURL,
SigningKeysURL: z.cfg.ZanzanaServer.SigningKeysURL,
}),
),
)
@@ -218,14 +216,18 @@ func (z *Zanzana) start(ctx context.Context) error {
}
func (z *Zanzana) running(ctx context.Context) error {
if z.cfg.Env == setting.Dev && z.cfg.Zanzana.ListenHTTP {
go func() {
z.logger.Info("Starting OpenFGA HTTP server")
err := zserver.StartOpenFGAHttpSever(z.cfg, z.handle, z.logger)
if err != nil {
z.logger.Error("failed to start OpenFGA HTTP server", "error", err)
}
}()
if z.cfg.Env == setting.Dev && z.cfg.ZanzanaServer.OpenFGAHttpAddr != "" {
srv, err := zanzana.NewOpenFGAHttpServer(z.cfg.ZanzanaServer, z.handle)
if err != nil {
z.logger.Error("failed to create OpenFGA HTTP server", "error", err)
} else {
go func() {
z.logger.Info("Starting OpenFGA HTTP server")
if err := srv.ListenAndServe(); err != nil {
z.logger.Error("failed to start OpenFGA HTTP server", "error", err)
}
}()
}
}
// Run is blocking so we can just run it here
+5
View File
@@ -4,6 +4,7 @@ import (
"context"
"github.com/grafana/authlib/authz"
"google.golang.org/grpc"
authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
"github.com/grafana/grafana/pkg/services/authz/zanzana/client"
@@ -17,6 +18,10 @@ type Client interface {
BatchCheck(ctx context.Context, req *authzextv1.BatchCheckRequest) (*authzextv1.BatchCheckResponse, error)
}
func NewClient(cc grpc.ClientConnInterface) (*client.Client, error) {
return client.New(cc)
}
func NewNoopClient() *client.NoopClient {
return client.NewNoop()
}
+2 -41
View File
@@ -2,7 +2,6 @@ package client
import (
"context"
"fmt"
"github.com/grafana/authlib/authz"
authzv1 "github.com/grafana/authlib/authz/proto/v1"
@@ -13,66 +12,28 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/infra/log"
authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
"github.com/grafana/grafana/pkg/setting"
)
var _ authz.AccessClient = (*Client)(nil)
var tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/authz/zanzana/client")
type ClientOption func(c *Client)
func WithTenantID(tenantID string) ClientOption {
return func(c *Client) {
c.tenantID = tenantID
}
}
func WithLogger(logger log.Logger) ClientOption {
return func(c *Client) {
c.logger = logger
}
}
type Client struct {
logger log.Logger
authz authzv1.AuthzServiceClient
authzext authzextv1.AuthzExtentionServiceClient
tenantID string
}
func NewClient(ctx context.Context, cc grpc.ClientConnInterface, cfg *setting.Cfg) (*Client, error) {
stackID := cfg.StackID
if stackID == "" {
stackID = "default"
}
return New(
ctx,
cc,
WithTenantID(fmt.Sprintf("stacks-%s", stackID)),
WithLogger(log.New("zanzana-client")),
)
}
func New(ctx context.Context, cc grpc.ClientConnInterface, opts ...ClientOption) (*Client, error) {
func New(cc grpc.ClientConnInterface) (*Client, error) {
c := &Client{
authz: authzv1.NewAuthzServiceClient(cc),
authzext: authzextv1.NewAuthzExtentionServiceClient(cc),
}
for _, o := range opts {
o(c)
}
if c.logger == nil {
c.logger = log.NewNopLogger()
logger: log.New("zanzana-client"),
}
return c, nil
}
// Check implements authz.AccessClient.
func (c *Client) Check(ctx context.Context, id claims.AuthInfo, req authz.CheckRequest) (authz.CheckResponse, error) {
ctx, span := tracer.Start(ctx, "authz.zanzana.client.Check")
defer span.End()
+25
View File
@@ -1 +1,26 @@
package zanzana
import (
"net/http"
openfgav1 "github.com/openfga/api/proto/openfga/v1"
openfgaserver "github.com/openfga/openfga/pkg/server"
openfgastorage "github.com/openfga/openfga/pkg/storage"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/authz/zanzana/server"
"github.com/grafana/grafana/pkg/services/grpcserver"
"github.com/grafana/grafana/pkg/setting"
)
func NewServer(cfg setting.ZanzanaServerSettings, openfga openfgav1.OpenFGAServiceServer, logger log.Logger) (*server.Server, error) {
return server.NewServer(cfg, openfga, logger)
}
func NewOpenFGAServer(cfg setting.ZanzanaServerSettings, store openfgastorage.OpenFGADatastore, logger log.Logger) (*openfgaserver.Server, error) {
return server.NewOpenFGAServer(cfg, store, logger)
}
func NewOpenFGAHttpServer(cfg setting.ZanzanaServerSettings, srv grpcserver.Provider) (*http.Server, error) {
return server.NewOpenFGAHttpServer(cfg, srv)
}
@@ -14,7 +14,6 @@ import (
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/rs/cors"
"go.uber.org/zap/zapcore"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
healthv1pb "google.golang.org/grpc/health/grpc_health_v1"
@@ -27,7 +26,7 @@ import (
zlogger "github.com/grafana/grafana/pkg/services/authz/zanzana/logger"
)
func NewOpenFGA(cfg *setting.ZanzanaSettings, store storage.OpenFGADatastore, logger log.Logger) (*server.Server, error) {
func NewOpenFGAServer(cfg setting.ZanzanaServerSettings, store storage.OpenFGADatastore, logger log.Logger) (*server.Server, error) {
opts := []server.OpenFGAServiceV1Option{
server.WithDatastore(store),
server.WithLogger(zlogger.New(logger)),
@@ -48,8 +47,7 @@ func NewOpenFGA(cfg *setting.ZanzanaSettings, store storage.OpenFGADatastore, lo
return srv, nil
}
// StartOpenFGAHttpSever starts HTTP server which allows to use fga cli.
func StartOpenFGAHttpSever(cfg *setting.Cfg, srv grpcserver.Provider, logger log.Logger) error {
func NewOpenFGAHttpServer(cfg setting.ZanzanaServerSettings, srv grpcserver.Provider) (*http.Server, error) {
dialOpts := []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
}
@@ -66,12 +64,12 @@ func StartOpenFGAHttpSever(cfg *setting.Cfg, srv grpcserver.Provider, logger log
retries++
}
if addr == "" {
return fmt.Errorf("failed to start HTTP server: GRPC server unavailable")
return nil, fmt.Errorf("failed to create HTTP server: GRPC server unavailable")
}
conn, err := grpc.NewClient(addr, dialOpts...)
if err != nil {
return fmt.Errorf("unable to dial GRPC: %w", err)
return nil, fmt.Errorf("unable to dial GRPC: %w", err)
}
muxOpts := []runtime.ServeMuxOption{
@@ -91,11 +89,11 @@ func StartOpenFGAHttpSever(cfg *setting.Cfg, srv grpcserver.Provider, logger log
}
mux := runtime.NewServeMux(muxOpts...)
if err := openfgav1.RegisterOpenFGAServiceHandler(context.TODO(), mux, conn); err != nil {
return fmt.Errorf("failed to register gateway handler: %w", err)
return nil, fmt.Errorf("failed to register gateway handler: %w", err)
}
httpServer := &http.Server{
Addr: cfg.Zanzana.HttpAddr,
return &http.Server{
Addr: cfg.OpenFGAHttpAddr,
Handler: cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowCredentials: true,
@@ -104,13 +102,5 @@ func StartOpenFGAHttpSever(cfg *setting.Cfg, srv grpcserver.Provider, logger log
http.MethodHead, http.MethodPatch, http.MethodDelete, http.MethodPut},
}).Handler(mux),
ReadHeaderTimeout: 30 * time.Second,
}
go func() {
err = httpServer.ListenAndServe()
if err != nil {
logger.Error("failed to start http server", zapcore.Field{Key: "err", Type: zapcore.ErrorType, Interface: err})
}
}()
logger.Info(fmt.Sprintf("OpenFGA HTTP server listening on '%s'...", httpServer.Addr))
return nil
}, nil
}
+5 -32
View File
@@ -9,7 +9,6 @@ import (
"github.com/fullstorydev/grpchan/inprocgrpc"
authzv1 "github.com/grafana/authlib/authz/proto/v1"
openfgav1 "github.com/openfga/api/proto/openfga/v1"
"github.com/openfga/language/pkg/go/transformer"
"go.opentelemetry.io/otel"
dashboardalpha1 "github.com/grafana/grafana/pkg/apis/dashboard/v2alpha1"
@@ -34,9 +33,8 @@ type Server struct {
openfga openfgav1.OpenFGAServiceServer
openfgaClient openfgav1.OpenFGAServiceClient
cfg setting.ZanzanaSettings
cfg setting.ZanzanaServerSettings
logger log.Logger
modules []transformer.ModuleFile
stores map[string]storeInfo
storesMU *sync.Mutex
cache *localcache.CacheService
@@ -47,25 +45,7 @@ type storeInfo struct {
ModelID string
}
type ServerOption func(s *Server)
func WithLogger(logger log.Logger) ServerOption {
return func(s *Server) {
s.logger = logger
}
}
func WithSchema(modules []transformer.ModuleFile) ServerOption {
return func(s *Server) {
s.modules = modules
}
}
func NewAuthzServer(cfg *setting.Cfg, openfga openfgav1.OpenFGAServiceServer) (*Server, error) {
return NewAuthz(cfg, openfga)
}
func NewAuthz(cfg *setting.Cfg, openfga openfgav1.OpenFGAServiceServer, opts ...ServerOption) (*Server, error) {
func NewServer(cfg setting.ZanzanaServerSettings, openfga openfgav1.OpenFGAServiceServer, logger log.Logger) (*Server, error) {
channel := &inprocgrpc.Channel{}
openfgav1.RegisterOpenFGAServiceServer(channel, openfga)
openFGAClient := openfgav1.NewOpenFGAServiceClient(channel)
@@ -75,16 +55,9 @@ func NewAuthz(cfg *setting.Cfg, openfga openfgav1.OpenFGAServiceServer, opts ...
openfgaClient: openFGAClient,
storesMU: &sync.Mutex{},
stores: make(map[string]storeInfo),
cfg: cfg.Zanzana,
cache: localcache.New(cfg.Zanzana.CheckQueryCacheTTL, cacheCleanInterval),
}
for _, o := range opts {
o(s)
}
if s.logger == nil {
s.logger = log.New("authz-server")
cfg: cfg,
cache: localcache.New(cfg.CheckQueryCacheTTL, cacheCleanInterval),
logger: logger,
}
return s, nil
@@ -71,10 +71,10 @@ func setup(t *testing.T, testDB db.DB, cfg *setting.Cfg) *Server {
t.Helper()
store, err := store.NewEmbeddedStore(cfg, testDB, log.NewNopLogger())
require.NoError(t, err)
openfga, err := NewOpenFGA(&cfg.Zanzana, store, log.NewNopLogger())
openfga, err := NewOpenFGAServer(cfg.ZanzanaServer, store, log.NewNopLogger())
require.NoError(t, err)
srv, err := NewAuthz(cfg, openfga)
srv, err := NewServer(cfg.ZanzanaServer, openfga, log.NewNopLogger())
require.NoError(t, err)
storeInf, err := srv.getStoreInfo(context.Background(), namespace)