From 729bfb31f193d521abf3f2aaacc7f27cd845ab7d Mon Sep 17 00:00:00 2001 From: Vikrant Gupta Date: Tue, 2 Sep 2025 16:30:47 +0530 Subject: [PATCH] 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 --- pkg/authz/openfgaauthz/provider.go | 110 +++++++++++------- pkg/authz/openfgaauthz/schema/base.fga | 35 ------ pkg/authz/openfgaauthz/schema/schema.go | 19 --- .../openfgaauthz/{store.go => sqlstore.go} | 2 +- pkg/authz/openfgaschema/base.fga | 13 +++ pkg/authz/openfgaschema/schema.go | 29 +++++ pkg/authz/schema.go | 11 ++ 7 files changed, 125 insertions(+), 94 deletions(-) delete mode 100644 pkg/authz/openfgaauthz/schema/base.fga delete mode 100644 pkg/authz/openfgaauthz/schema/schema.go rename pkg/authz/openfgaauthz/{store.go => sqlstore.go} (94%) create mode 100644 pkg/authz/openfgaschema/base.fga create mode 100644 pkg/authz/openfgaschema/schema.go create mode 100644 pkg/authz/schema.go diff --git a/pkg/authz/openfgaauthz/provider.go b/pkg/authz/openfgaauthz/provider.go index a9d9636ae185..85987a9ab68c 100644 --- a/pkg/authz/openfgaauthz/provider.go +++ b/pkg/authz/openfgaauthz/provider.go @@ -4,66 +4,67 @@ import ( "context" 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/sqlstore" openfgav1 "github.com/openfga/api/proto/openfga/v1" - language "github.com/openfga/language/pkg/go/transformer" - "github.com/openfga/openfga/pkg/server" + openfgapkgtransformer "github.com/openfga/language/pkg/go/transformer" + openfgapkgserver "github.com/openfga/openfga/pkg/server" + "google.golang.org/protobuf/encoding/protojson" ) type provider struct { - config authz.Config - settings factory.ScopedProviderSettings - server *server.Server - stopChan chan struct{} + config authz.Config + settings factory.ScopedProviderSettings + openfgaSchema []openfgapkgtransformer.ModuleFile + openfgaServer *openfgapkgserver.Server + 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 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") // setup connections and run the migrations - sqlstore, err := NewStore(storeConfig{sqlstoreConfig: sqlstoreConfig}) + sqlstore, err := NewSQLStore(storeConfig{sqlstoreConfig: sqlstoreConfig}) if err != nil { scopedProviderSettings.Logger().DebugContext(ctx, "failed to initialize sqlstore for authz") return nil, err } // setup the openfga server - opts := []server.OpenFGAServiceV1Option{ - server.WithDatastore(sqlstore), - server.WithLogger(NewLogger(scopedProviderSettings.Logger())), + opts := []openfgapkgserver.OpenFGAServiceV1Option{ + openfgapkgserver.WithDatastore(sqlstore), + openfgapkgserver.WithLogger(NewLogger(scopedProviderSettings.Logger())), } - openfgaServer, err := server.NewServerWithOpts(opts...) + openfgaServer, err := openfgapkgserver.NewServerWithOpts(opts...) if err != nil { scopedProviderSettings.Logger().DebugContext(ctx, "failed to create authz server") return nil, err } return &provider{ - config: config, - settings: scopedProviderSettings, - server: openfgaServer, - stopChan: make(chan struct{}), + config: config, + settings: scopedProviderSettings, + openfgaServer: openfgaServer, + openfgaSchema: openfgaSchema, + stopChan: make(chan struct{}), }, nil } func (provider *provider) Start(ctx context.Context) error { - storeId, err := provider.getOrCreateStore(ctx) + storeId, err := provider.getOrCreateStore(ctx, "signoz") if err != nil { - provider.settings.Logger().DebugContext(ctx, "failed to getOrCreateStore") return err } - err = provider.getOrCreateAuthorisationModel(ctx, storeId) + _, err = provider.getOrCreateModel(ctx, storeId) if err != nil { - provider.settings.Logger().DebugContext(ctx, "failed to getOrCreateAuthorisationModel") return err } @@ -72,24 +73,24 @@ func (provider *provider) Start(ctx context.Context) error { } func (provider *provider) Stop(ctx context.Context) error { - provider.server.Close() + provider.openfgaServer.Close() close(provider.stopChan) return nil } -func (provider *provider) getOrCreateStore(ctx context.Context) (string, error) { - stores, err := provider.server.ListStores(ctx, &openfgav1.ListStoresRequest{}) +func (provider *provider) getOrCreateStore(ctx context.Context, name string) (string, error) { + stores, err := provider.openfgaServer.ListStores(ctx, &openfgav1.ListStoresRequest{}) if err != nil { return "", err } for _, store := range stores.GetStores() { - if store.GetName() == "signoz" { + if store.GetName() == name { 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 { return "", err } @@ -97,32 +98,63 @@ func (provider *provider) getOrCreateStore(ctx context.Context) (string, error) return store.Id, nil } -func (provider *provider) getOrCreateAuthorisationModel(ctx context.Context, storeId string) error { - schema, err := language.TransformModuleFilesToModel(schema.SchemaModules, "1.1") +func (provider *provider) getOrCreateModel(ctx context.Context, storeID string) (string, error) { + schema, err := openfgapkgtransformer.TransformModuleFilesToModel(provider.openfgaSchema, "1.1") 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 { - return err + return "", err } for _, authModel := range authorisationModels.GetAuthorizationModels() { - if authModel.Id == schema.Id { - return nil + equal, err := provider.isModelEqual(schema, authModel) + if err != nil { + return "", err + } + if equal { + return authModel.Id, nil } } - _, err = provider.server.WriteAuthorizationModel(ctx, &openfgav1.WriteAuthorizationModelRequest{ - StoreId: storeId, + authorizationModel, err := provider.openfgaServer.WriteAuthorizationModel(ctx, &openfgav1.WriteAuthorizationModelRequest{ + StoreId: storeID, TypeDefinitions: schema.TypeDefinitions, SchemaVersion: schema.SchemaVersion, Conditions: schema.Conditions, }) 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 } diff --git a/pkg/authz/openfgaauthz/schema/base.fga b/pkg/authz/openfgaauthz/schema/base.fga deleted file mode 100644 index 729ea4e59201..000000000000 --- a/pkg/authz/openfgaauthz/schema/base.fga +++ /dev/null @@ -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] \ No newline at end of file diff --git a/pkg/authz/openfgaauthz/schema/schema.go b/pkg/authz/openfgaauthz/schema/schema.go deleted file mode 100644 index b9eac510a082..000000000000 --- a/pkg/authz/openfgaauthz/schema/schema.go +++ /dev/null @@ -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, - }, -} diff --git a/pkg/authz/openfgaauthz/store.go b/pkg/authz/openfgaauthz/sqlstore.go similarity index 94% rename from pkg/authz/openfgaauthz/store.go rename to pkg/authz/openfgaauthz/sqlstore.go index ca9eb0b9c395..6232a5cccd27 100644 --- a/pkg/authz/openfgaauthz/store.go +++ b/pkg/authz/openfgaauthz/sqlstore.go @@ -14,7 +14,7 @@ type storeConfig struct { sqlstoreConfig sqlstore.Config } -func NewStore(cfg storeConfig) (storage.OpenFGADatastore, error) { +func NewSQLStore(cfg storeConfig) (storage.OpenFGADatastore, error) { switch cfg.sqlstoreConfig.Provider { case "sqlite": err := migrate.RunMigrations(migrate.MigrationConfig{Engine: cfg.sqlstoreConfig.Provider, URI: "file:" + cfg.sqlstoreConfig.Sqlite.Path + "?_foreign_keys=true"}) diff --git a/pkg/authz/openfgaschema/base.fga b/pkg/authz/openfgaschema/base.fga new file mode 100644 index 000000000000..7275c3137c59 --- /dev/null +++ b/pkg/authz/openfgaschema/base.fga @@ -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 \ No newline at end of file diff --git a/pkg/authz/openfgaschema/schema.go b/pkg/authz/openfgaschema/schema.go new file mode 100644 index 000000000000..605cad0501f1 --- /dev/null +++ b/pkg/authz/openfgaschema/schema.go @@ -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, + }, + } +} diff --git a/pkg/authz/schema.go b/pkg/authz/schema.go new file mode 100644 index 000000000000..39bf115e8316 --- /dev/null +++ b/pkg/authz/schema.go @@ -0,0 +1,11 @@ +package authz + +import ( + "context" + + openfgapkgtransformer "github.com/openfga/language/pkg/go/transformer" +) + +type Schema interface { + Get(context.Context) []openfgapkgtransformer.ModuleFile +}