Chore: Init auth model on server side (#95142)
* Chore: Init auth model on server side * fix linter
This commit is contained in:
@@ -56,7 +56,10 @@ func ProvideZanzana(cfg *setting.Cfg, db db.DB, features featuremgmt.FeatureTogg
|
|||||||
return nil, fmt.Errorf("failed to start zanzana: %w", err)
|
return nil, fmt.Errorf("failed to start zanzana: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
srv := zanzana.NewAuthzServer(openfga)
|
srv, err := zanzana.NewAuthzServer(cfg, openfga)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to start zanzana: %w", err)
|
||||||
|
}
|
||||||
channel := &inprocgrpc.Channel{}
|
channel := &inprocgrpc.Channel{}
|
||||||
openfgav1.RegisterOpenFGAServiceServer(channel, openfga)
|
openfgav1.RegisterOpenFGAServiceServer(channel, openfga)
|
||||||
authzv1.RegisterAuthzServiceServer(channel, srv)
|
authzv1.RegisterAuthzServiceServer(channel, srv)
|
||||||
@@ -114,7 +117,10 @@ func (z *Zanzana) start(ctx context.Context) error {
|
|||||||
return fmt.Errorf("failed to start zanzana: %w", err)
|
return fmt.Errorf("failed to start zanzana: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
srv := zanzana.NewAuthzServer(openfga)
|
srv, err := zanzana.NewAuthzServer(z.cfg, openfga)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to start zanzana: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
tracingCfg, err := tracing.ProvideTracingConfig(z.cfg)
|
tracingCfg, err := tracing.ProvideTracingConfig(z.cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package zanzana
|
package zanzana
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||||
"github.com/openfga/openfga/pkg/server"
|
"github.com/openfga/openfga/pkg/server"
|
||||||
"github.com/openfga/openfga/pkg/storage"
|
"github.com/openfga/openfga/pkg/storage"
|
||||||
@@ -16,8 +18,16 @@ func NewOpenFGAServer(cfg *setting.Cfg, store storage.OpenFGADatastore, logger l
|
|||||||
return zserver.NewOpenFGA(&cfg.Zanzana, store, logger)
|
return zserver.NewOpenFGA(&cfg.Zanzana, store, logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthzServer(openfga openfgav1.OpenFGAServiceServer) *zserver.Server {
|
func NewAuthzServer(cfg *setting.Cfg, openfga openfgav1.OpenFGAServiceServer) (*zserver.Server, error) {
|
||||||
return zserver.NewAuthz(openfga)
|
stackID := cfg.StackID
|
||||||
|
if stackID == "" {
|
||||||
|
stackID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
return zserver.NewAuthz(
|
||||||
|
openfga,
|
||||||
|
zserver.WithTenantID(fmt.Sprintf("stack-%s", stackID)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartOpenFGAHttpSever(cfg *setting.Cfg, srv grpcserver.Provider, logger log.Logger) error {
|
func StartOpenFGAHttpSever(cfg *setting.Cfg, srv grpcserver.Provider, logger log.Logger) error {
|
||||||
|
|||||||
@@ -2,12 +2,18 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
authzv1 "github.com/grafana/authlib/authz/proto/v1"
|
authzv1 "github.com/grafana/authlib/authz/proto/v1"
|
||||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||||
|
"github.com/openfga/language/pkg/go/transformer"
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
|
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
authzextv1 "github.com/grafana/grafana/pkg/services/authz/zanzana/proto/v1"
|
authzextv1 "github.com/grafana/grafana/pkg/services/authz/zanzana/proto/v1"
|
||||||
|
"github.com/grafana/grafana/pkg/services/authz/zanzana/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ authzv1.AuthzServiceServer = (*Server)(nil)
|
var _ authzv1.AuthzServiceServer = (*Server)(nil)
|
||||||
@@ -15,18 +21,179 @@ var _ authzextv1.AuthzExtentionServiceServer = (*Server)(nil)
|
|||||||
|
|
||||||
var tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/authz/zanzana/server")
|
var tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/authz/zanzana/server")
|
||||||
|
|
||||||
func NewAuthz(openfga openfgav1.OpenFGAServiceServer) *Server {
|
var errStoreNotFound = errors.New("store not found")
|
||||||
return &Server{openfga: openfga}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
authzv1.UnimplementedAuthzServiceServer
|
authzv1.UnimplementedAuthzServiceServer
|
||||||
authzextv1.UnimplementedAuthzExtentionServiceServer
|
authzextv1.UnimplementedAuthzExtentionServiceServer
|
||||||
|
|
||||||
openfga openfgav1.OpenFGAServiceServer
|
openfga openfgav1.OpenFGAServiceServer
|
||||||
|
|
||||||
|
logger log.Logger
|
||||||
|
modules []transformer.ModuleFile
|
||||||
|
tenantID string
|
||||||
|
storeID string
|
||||||
|
modelID string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerOption func(s *Server)
|
||||||
|
|
||||||
|
func WithTenantID(tenantID string) ServerOption {
|
||||||
|
return func(s *Server) {
|
||||||
|
s.tenantID = tenantID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 NewAuthz(openfga openfgav1.OpenFGAServiceServer, opts ...ServerOption) (*Server, error) {
|
||||||
|
s := &Server{openfga: openfga}
|
||||||
|
|
||||||
|
for _, o := range opts {
|
||||||
|
o(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.logger == nil {
|
||||||
|
s.logger = log.New("authz-server")
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.tenantID == "" {
|
||||||
|
s.tenantID = "stack-default"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s.modules) == 0 {
|
||||||
|
s.modules = schema.SchemaModules
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
store, err := s.getOrCreateStore(ctx, s.tenantID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.storeID = store.GetId()
|
||||||
|
|
||||||
|
modelID, err := s.loadModel(ctx, s.storeID, s.modules)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.modelID = modelID
|
||||||
|
|
||||||
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Check(ctx context.Context, r *authzv1.CheckRequest) (*authzv1.CheckResponse, error) {
|
func (s *Server) Check(ctx context.Context, r *authzv1.CheckRequest) (*authzv1.CheckResponse, error) {
|
||||||
tracer.Start(ctx, "authzServer.Check")
|
tracer.Start(ctx, "authzServer.Check")
|
||||||
|
|
||||||
return &authzv1.CheckResponse{}, nil
|
return &authzv1.CheckResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) getOrCreateStore(ctx context.Context, name string) (*openfgav1.Store, error) {
|
||||||
|
store, err := s.getStore(ctx, name)
|
||||||
|
|
||||||
|
if errors.Is(err, errStoreNotFound) {
|
||||||
|
var res *openfgav1.CreateStoreResponse
|
||||||
|
res, err = s.openfga.CreateStore(ctx, &openfgav1.CreateStoreRequest{Name: name})
|
||||||
|
if res != nil {
|
||||||
|
store = &openfgav1.Store{
|
||||||
|
Id: res.GetId(),
|
||||||
|
Name: res.GetName(),
|
||||||
|
CreatedAt: res.GetCreatedAt(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return store, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) getStore(ctx context.Context, name string) (*openfgav1.Store, error) {
|
||||||
|
var continuationToken string
|
||||||
|
|
||||||
|
// OpenFGA client does not support any filters for stores.
|
||||||
|
// We should create an issue to support some way to get stores by name.
|
||||||
|
// For now we need to go thourh all stores until we find a match or we hit the end.
|
||||||
|
for {
|
||||||
|
res, err := s.openfga.ListStores(ctx, &openfgav1.ListStoresRequest{
|
||||||
|
PageSize: &wrapperspb.Int32Value{Value: 20},
|
||||||
|
ContinuationToken: continuationToken,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to initiate zanzana tenant: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range res.GetStores() {
|
||||||
|
if s.GetName() == name {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have no more stores to check
|
||||||
|
if res.GetContinuationToken() == "" {
|
||||||
|
return nil, errStoreNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
continuationToken = res.GetContinuationToken()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) loadModel(ctx context.Context, storeID string, modules []transformer.ModuleFile) (string, error) {
|
||||||
|
var continuationToken string
|
||||||
|
|
||||||
|
model, err := schema.TransformModulesToModel(modules)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
// ReadAuthorizationModels returns authorization models for a store sorted in descending order of creation.
|
||||||
|
// So with a pageSize of 1 we will get the latest model.
|
||||||
|
res, err := s.openfga.ReadAuthorizationModels(ctx, &openfgav1.ReadAuthorizationModelsRequest{
|
||||||
|
StoreId: storeID,
|
||||||
|
PageSize: &wrapperspb.Int32Value{Value: 20},
|
||||||
|
ContinuationToken: continuationToken,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to load authorization model: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range res.GetAuthorizationModels() {
|
||||||
|
// If provided dsl is equal to a stored dsl we use that as the authorization id
|
||||||
|
if schema.EqualModels(m, model) {
|
||||||
|
return m.GetId(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have not found any matching authorization model we break the loop and create a new one
|
||||||
|
if res.GetContinuationToken() == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
continuationToken = res.GetContinuationToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
writeRes, err := s.openfga.WriteAuthorizationModel(ctx, &openfgav1.WriteAuthorizationModelRequest{
|
||||||
|
StoreId: s.storeID,
|
||||||
|
TypeDefinitions: model.GetTypeDefinitions(),
|
||||||
|
SchemaVersion: model.GetSchemaVersion(),
|
||||||
|
Conditions: model.GetConditions(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to load authorization model: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeRes.GetAuthorizationModelId(), nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user