Chore: Init auth model on server side (#95142)

* Chore: Init auth model on server side

* fix linter
This commit is contained in:
Alexander Zobnin 2024-10-22 14:50:52 +02:00 committed by GitHub
parent 986bd2f9f8
commit e709de603d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 190 additions and 7 deletions

View File

@ -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)
}
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{}
openfgav1.RegisterOpenFGAServiceServer(channel, openfga)
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)
}
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)
if err != nil {

View File

@ -1,6 +1,8 @@
package zanzana
import (
"fmt"
openfgav1 "github.com/openfga/api/proto/openfga/v1"
"github.com/openfga/openfga/pkg/server"
"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)
}
func NewAuthzServer(openfga openfgav1.OpenFGAServiceServer) *zserver.Server {
return zserver.NewAuthz(openfga)
func NewAuthzServer(cfg *setting.Cfg, openfga openfgav1.OpenFGAServiceServer) (*zserver.Server, error) {
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 {

View File

@ -2,12 +2,18 @@ package server
import (
"context"
"errors"
"fmt"
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"
"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"
"github.com/grafana/grafana/pkg/services/authz/zanzana/schema"
)
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")
func NewAuthz(openfga openfgav1.OpenFGAServiceServer) *Server {
return &Server{openfga: openfga}
}
var errStoreNotFound = errors.New("store not found")
type Server struct {
authzv1.UnimplementedAuthzServiceServer
authzextv1.UnimplementedAuthzExtentionServiceServer
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) {
tracer.Start(ctx, "authzServer.Check")
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
}