feat(authz): implement the current usecases in openfga (#8982)

* feat(authz): implement the current usecases in openfga

* feat(authz): implement the current usecases in openfga

* feat(authz): extract out the schema and DI the same

* feat(authz): extract out the schema and DI the same
This commit is contained in:
Vikrant Gupta 2025-09-02 16:30:47 +05:30 committed by GitHub
parent 052fb8b703
commit 729bfb31f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 125 additions and 94 deletions

View File

@ -4,43 +4,45 @@ import (
"context" "context"
authz "github.com/SigNoz/signoz/pkg/authz" authz "github.com/SigNoz/signoz/pkg/authz"
"github.com/SigNoz/signoz/pkg/authz/openfgaauthz/schema"
"github.com/SigNoz/signoz/pkg/factory" "github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/sqlstore" "github.com/SigNoz/signoz/pkg/sqlstore"
openfgav1 "github.com/openfga/api/proto/openfga/v1" openfgav1 "github.com/openfga/api/proto/openfga/v1"
language "github.com/openfga/language/pkg/go/transformer" openfgapkgtransformer "github.com/openfga/language/pkg/go/transformer"
"github.com/openfga/openfga/pkg/server" openfgapkgserver "github.com/openfga/openfga/pkg/server"
"google.golang.org/protobuf/encoding/protojson"
) )
type provider struct { type provider struct {
config authz.Config config authz.Config
settings factory.ScopedProviderSettings settings factory.ScopedProviderSettings
server *server.Server openfgaSchema []openfgapkgtransformer.ModuleFile
openfgaServer *openfgapkgserver.Server
stopChan chan struct{} stopChan chan struct{}
} }
func NewProviderFactory(sqlstoreConfig sqlstore.Config) factory.ProviderFactory[authz.AuthZ, authz.Config] { func NewProviderFactory(sqlstoreConfig sqlstore.Config, openfgaSchema []openfgapkgtransformer.ModuleFile) factory.ProviderFactory[authz.AuthZ, authz.Config] {
return factory.NewProviderFactory(factory.MustNewName("openfga"), func(ctx context.Context, ps factory.ProviderSettings, config authz.Config) (authz.AuthZ, error) { return factory.NewProviderFactory(factory.MustNewName("openfga"), func(ctx context.Context, ps factory.ProviderSettings, config authz.Config) (authz.AuthZ, error) {
return newOpenfgaProvider(ctx, ps, config, sqlstoreConfig) return newOpenfgaProvider(ctx, ps, config, sqlstoreConfig, openfgaSchema)
}) })
} }
func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, config authz.Config, sqlstoreConfig sqlstore.Config) (authz.AuthZ, error) { func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, config authz.Config, sqlstoreConfig sqlstore.Config, openfgaSchema []openfgapkgtransformer.ModuleFile) (authz.AuthZ, error) {
scopedProviderSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/authz/openfgaauthz") scopedProviderSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/authz/openfgaauthz")
// setup connections and run the migrations // setup connections and run the migrations
sqlstore, err := NewStore(storeConfig{sqlstoreConfig: sqlstoreConfig}) sqlstore, err := NewSQLStore(storeConfig{sqlstoreConfig: sqlstoreConfig})
if err != nil { if err != nil {
scopedProviderSettings.Logger().DebugContext(ctx, "failed to initialize sqlstore for authz") scopedProviderSettings.Logger().DebugContext(ctx, "failed to initialize sqlstore for authz")
return nil, err return nil, err
} }
// setup the openfga server // setup the openfga server
opts := []server.OpenFGAServiceV1Option{ opts := []openfgapkgserver.OpenFGAServiceV1Option{
server.WithDatastore(sqlstore), openfgapkgserver.WithDatastore(sqlstore),
server.WithLogger(NewLogger(scopedProviderSettings.Logger())), openfgapkgserver.WithLogger(NewLogger(scopedProviderSettings.Logger())),
} }
openfgaServer, err := server.NewServerWithOpts(opts...) openfgaServer, err := openfgapkgserver.NewServerWithOpts(opts...)
if err != nil { if err != nil {
scopedProviderSettings.Logger().DebugContext(ctx, "failed to create authz server") scopedProviderSettings.Logger().DebugContext(ctx, "failed to create authz server")
return nil, err return nil, err
@ -49,21 +51,20 @@ func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings,
return &provider{ return &provider{
config: config, config: config,
settings: scopedProviderSettings, settings: scopedProviderSettings,
server: openfgaServer, openfgaServer: openfgaServer,
openfgaSchema: openfgaSchema,
stopChan: make(chan struct{}), stopChan: make(chan struct{}),
}, nil }, nil
} }
func (provider *provider) Start(ctx context.Context) error { func (provider *provider) Start(ctx context.Context) error {
storeId, err := provider.getOrCreateStore(ctx) storeId, err := provider.getOrCreateStore(ctx, "signoz")
if err != nil { if err != nil {
provider.settings.Logger().DebugContext(ctx, "failed to getOrCreateStore")
return err return err
} }
err = provider.getOrCreateAuthorisationModel(ctx, storeId) _, err = provider.getOrCreateModel(ctx, storeId)
if err != nil { if err != nil {
provider.settings.Logger().DebugContext(ctx, "failed to getOrCreateAuthorisationModel")
return err return err
} }
@ -72,24 +73,24 @@ func (provider *provider) Start(ctx context.Context) error {
} }
func (provider *provider) Stop(ctx context.Context) error { func (provider *provider) Stop(ctx context.Context) error {
provider.server.Close() provider.openfgaServer.Close()
close(provider.stopChan) close(provider.stopChan)
return nil return nil
} }
func (provider *provider) getOrCreateStore(ctx context.Context) (string, error) { func (provider *provider) getOrCreateStore(ctx context.Context, name string) (string, error) {
stores, err := provider.server.ListStores(ctx, &openfgav1.ListStoresRequest{}) stores, err := provider.openfgaServer.ListStores(ctx, &openfgav1.ListStoresRequest{})
if err != nil { if err != nil {
return "", err return "", err
} }
for _, store := range stores.GetStores() { for _, store := range stores.GetStores() {
if store.GetName() == "signoz" { if store.GetName() == name {
return store.Id, nil return store.Id, nil
} }
} }
store, err := provider.server.CreateStore(ctx, &openfgav1.CreateStoreRequest{Name: "signoz"}) store, err := provider.openfgaServer.CreateStore(ctx, &openfgav1.CreateStoreRequest{Name: name})
if err != nil { if err != nil {
return "", err return "", err
} }
@ -97,32 +98,63 @@ func (provider *provider) getOrCreateStore(ctx context.Context) (string, error)
return store.Id, nil return store.Id, nil
} }
func (provider *provider) getOrCreateAuthorisationModel(ctx context.Context, storeId string) error { func (provider *provider) getOrCreateModel(ctx context.Context, storeID string) (string, error) {
schema, err := language.TransformModuleFilesToModel(schema.SchemaModules, "1.1") schema, err := openfgapkgtransformer.TransformModuleFilesToModel(provider.openfgaSchema, "1.1")
if err != nil { if err != nil {
return err return "", err
} }
authorisationModels, err := provider.server.ReadAuthorizationModels(ctx, &openfgav1.ReadAuthorizationModelsRequest{StoreId: storeId}) authorisationModels, err := provider.openfgaServer.ReadAuthorizationModels(ctx, &openfgav1.ReadAuthorizationModelsRequest{StoreId: storeID})
if err != nil { if err != nil {
return err return "", err
} }
for _, authModel := range authorisationModels.GetAuthorizationModels() { for _, authModel := range authorisationModels.GetAuthorizationModels() {
if authModel.Id == schema.Id { equal, err := provider.isModelEqual(schema, authModel)
return nil if err != nil {
return "", err
}
if equal {
return authModel.Id, nil
} }
} }
_, err = provider.server.WriteAuthorizationModel(ctx, &openfgav1.WriteAuthorizationModelRequest{ authorizationModel, err := provider.openfgaServer.WriteAuthorizationModel(ctx, &openfgav1.WriteAuthorizationModelRequest{
StoreId: storeId, StoreId: storeID,
TypeDefinitions: schema.TypeDefinitions, TypeDefinitions: schema.TypeDefinitions,
SchemaVersion: schema.SchemaVersion, SchemaVersion: schema.SchemaVersion,
Conditions: schema.Conditions, Conditions: schema.Conditions,
}) })
if err != nil { if err != nil {
return err return "", err
} }
return nil return authorizationModel.AuthorizationModelId, nil
}
// the language model doesn't have any equality check
// https://github.com/openfga/language/blob/main/pkg/go/transformer/module-to-model_test.go#L38
func (provider *provider) isModelEqual(expected *openfgav1.AuthorizationModel, actual *openfgav1.AuthorizationModel) (bool, error) {
// we need to initialize a new model since the model extracted from schema doesn't have id
expectedAuthModel := openfgav1.AuthorizationModel{
SchemaVersion: expected.SchemaVersion,
TypeDefinitions: expected.TypeDefinitions,
Conditions: expected.Conditions,
}
expectedAuthModelBytes, err := protojson.Marshal(&expectedAuthModel)
if err != nil {
return false, err
}
actualAuthModel := openfgav1.AuthorizationModel{
SchemaVersion: actual.SchemaVersion,
TypeDefinitions: actual.TypeDefinitions,
Conditions: actual.Conditions,
}
actualAuthModelBytes, err := protojson.Marshal(&actualAuthModel)
if err != nil {
return false, err
}
return string(expectedAuthModelBytes) == string(actualAuthModelBytes), nil
} }

View File

@ -1,35 +0,0 @@
module base
type system
relations
define super_admin: [user]
type organisation
relations
define system: [system]
define owner: super_admin from system
define admin: [user, role#assignee, team#member]
define can_create: owner
type service_account
type user
type team
relations
define admin: [user]
define member: [user] or admin
define assignee: [user]
type role
relations
define assignee: [user]
type dashboard
relations
define admin: [user, service_account, role#assignee, team#member]
define editor: [user, service_account, role#assignee, team#member] or admin
define viewer: [user, service_account, role#assignee, team#member] or editor
define public_acess_view: [user, team#member]
define public_acess_edit: [user, team#member]

View File

@ -1,19 +0,0 @@
package schema
import (
_ "embed"
"github.com/openfga/language/pkg/go/transformer"
)
var (
//go:embed base.fga
baseDSL string
)
var SchemaModules = []transformer.ModuleFile{
{
Name: "base.fga",
Contents: baseDSL,
},
}

View File

@ -14,7 +14,7 @@ type storeConfig struct {
sqlstoreConfig sqlstore.Config sqlstoreConfig sqlstore.Config
} }
func NewStore(cfg storeConfig) (storage.OpenFGADatastore, error) { func NewSQLStore(cfg storeConfig) (storage.OpenFGADatastore, error) {
switch cfg.sqlstoreConfig.Provider { switch cfg.sqlstoreConfig.Provider {
case "sqlite": case "sqlite":
err := migrate.RunMigrations(migrate.MigrationConfig{Engine: cfg.sqlstoreConfig.Provider, URI: "file:" + cfg.sqlstoreConfig.Sqlite.Path + "?_foreign_keys=true"}) err := migrate.RunMigrations(migrate.MigrationConfig{Engine: cfg.sqlstoreConfig.Provider, URI: "file:" + cfg.sqlstoreConfig.Sqlite.Path + "?_foreign_keys=true"})

View File

@ -0,0 +1,13 @@
module base
type user
type role
relations
define assignee: [user]
type organisation
relations
define admin: [role#assignee]
define editor: [role#assignee] or admin
define viewer: [role#assignee] or editor

View File

@ -0,0 +1,29 @@
package openfgaschema
import (
"context"
_ "embed"
"github.com/SigNoz/signoz/pkg/authz"
openfgapkgtransformer "github.com/openfga/language/pkg/go/transformer"
)
var (
//go:embed base.fga
baseDSL string
)
type schema struct{}
func NewSchema() authz.Schema {
return &schema{}
}
func (schema *schema) Get(ctx context.Context) []openfgapkgtransformer.ModuleFile {
return []openfgapkgtransformer.ModuleFile{
{
Name: "base.fga",
Contents: baseDSL,
},
}
}

11
pkg/authz/schema.go Normal file
View File

@ -0,0 +1,11 @@
package authz
import (
"context"
openfgapkgtransformer "github.com/openfga/language/pkg/go/transformer"
)
type Schema interface {
Get(context.Context) []openfgapkgtransformer.ModuleFile
}