From 3845dac6f6cca86074908bb8194efd08ef1b6830 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Mon, 3 Oct 2022 15:27:47 -0400 Subject: [PATCH] Add caveats flag to disable writing by default on all datastores --- internal/datastore/memdb/caveat.go | 6 ++++++ internal/datastore/memdb/memdb.go | 21 ++++++++++++++++----- internal/datastore/memdb/readonly.go | 7 ++++--- internal/services/v1/relationships.go | 22 +++++++++++++++++----- pkg/cmd/datastore/datastore.go | 10 +++++++++- pkg/cmd/datastore/zz_generated.options.go | 8 ++++++++ 6 files changed, 60 insertions(+), 14 deletions(-) diff --git a/internal/datastore/memdb/caveat.go b/internal/datastore/memdb/caveat.go index a94d7983f1..504792c462 100644 --- a/internal/datastore/memdb/caveat.go +++ b/internal/datastore/memdb/caveat.go @@ -1,6 +1,8 @@ package memdb import ( + "fmt" + "github.com/authzed/spicedb/pkg/datastore" core "github.com/authzed/spicedb/pkg/proto/core/v1" @@ -22,6 +24,10 @@ func (c *caveat) Unwrap() *core.Caveat { } func (r *memdbReader) ReadCaveatByName(name string) (*core.Caveat, error) { + if !r.enableCaveats { + return nil, fmt.Errorf("caveats are not enabled") + } + r.lockOrPanic() defer r.Unlock() diff --git a/internal/datastore/memdb/memdb.go b/internal/datastore/memdb/memdb.go index 7945da3eac..b1078e06dd 100644 --- a/internal/datastore/memdb/memdb.go +++ b/internal/datastore/memdb/memdb.go @@ -35,6 +35,15 @@ func NewMemdbDatastore( watchBufferLength uint16, revisionQuantization, gcWindow time.Duration, +) (datastore.Datastore, error) { + return NewMemdbDatastoreWithCaveatsOption(watchBufferLength, revisionQuantization, gcWindow, true) +} + +func NewMemdbDatastoreWithCaveatsOption( + watchBufferLength uint16, + revisionQuantization, + gcWindow time.Duration, + enableCaveats bool, ) (datastore.Datastore, error) { if revisionQuantization > gcWindow { return nil, errors.New("gc window must be larger than quantization interval") @@ -70,6 +79,7 @@ func NewMemdbDatastore( quantizationPeriod: decimal.NewFromInt(revisionQuantization.Nanoseconds()), watchBufferLength: watchBufferLength, uniqueID: uniqueID, + enableCaveats: enableCaveats, }, nil } @@ -84,6 +94,7 @@ type memdbDatastore struct { quantizationPeriod datastore.Revision watchBufferLength uint16 uniqueID string + enableCaveats bool } type snapshot struct { @@ -96,11 +107,11 @@ func (mdb *memdbDatastore) SnapshotReader(revision datastore.Revision) datastore defer mdb.RUnlock() if len(mdb.revisions) == 0 { - return &memdbReader{nil, nil, datastore.NoRevision, fmt.Errorf("memdb datastore is not ready")} + return &memdbReader{nil, nil, datastore.NoRevision, fmt.Errorf("memdb datastore is not ready"), mdb.enableCaveats} } if err := mdb.checkRevisionLocal(revision); err != nil { - return &memdbReader{nil, nil, datastore.NoRevision, err} + return &memdbReader{nil, nil, datastore.NoRevision, err, mdb.enableCaveats} } revIndex := sort.Search(len(mdb.revisions), func(i int) bool { @@ -114,7 +125,7 @@ func (mdb *memdbDatastore) SnapshotReader(revision datastore.Revision) datastore rev := mdb.revisions[revIndex] if rev.db == nil { - return &memdbReader{nil, nil, datastore.NoRevision, fmt.Errorf("memdb datastore is already closed")} + return &memdbReader{nil, nil, datastore.NoRevision, fmt.Errorf("memdb datastore is already closed"), mdb.enableCaveats} } snapshotRevision := rev.revision @@ -124,7 +135,7 @@ func (mdb *memdbDatastore) SnapshotReader(revision datastore.Revision) datastore return roTxn, nil } - return &memdbReader{noopTryLocker{}, txSrc, snapshotRevision, nil} + return &memdbReader{noopTryLocker{}, txSrc, snapshotRevision, nil, mdb.enableCaveats} } func (mdb *memdbDatastore) ReadWriteTx( @@ -160,7 +171,7 @@ func (mdb *memdbDatastore) ReadWriteTx( newRevision := revisionFromTimestamp(time.Now().UTC()) - rwt := &memdbReadWriteTx{memdbReader{&sync.Mutex{}, txSrc, datastore.NoRevision, nil}, newRevision} + rwt := &memdbReadWriteTx{memdbReader{&sync.Mutex{}, txSrc, datastore.NoRevision, nil, mdb.enableCaveats}, newRevision} if err := f(ctx, rwt); err != nil { mdb.Lock() if tx != nil { diff --git a/internal/datastore/memdb/readonly.go b/internal/datastore/memdb/readonly.go index 16225455d6..471633ac0e 100644 --- a/internal/datastore/memdb/readonly.go +++ b/internal/datastore/memdb/readonly.go @@ -17,9 +17,10 @@ type txFactory func() (*memdb.Txn, error) type memdbReader struct { TryLocker - txSource txFactory - revision datastore.Revision - initErr error + txSource txFactory + revision datastore.Revision + initErr error + enableCaveats bool } // QueryRelationships reads relationships starting from the resource side. diff --git a/internal/services/v1/relationships.go b/internal/services/v1/relationships.go index eef31451ac..2141cb7004 100644 --- a/internal/services/v1/relationships.go +++ b/internal/services/v1/relationships.go @@ -195,13 +195,25 @@ func (ps *permissionServer) WriteRelationships(ctx context.Context, req *v1.Writ for _, update := range req.Updates { // TODO(vroldanbet) we type assert CaveatReader for now because not all datastores implement it // eventually this type assertion should be removed - if caveatReader, ok := rwt.(datastore.CaveatReader); update.Relationship.OptionalCaveat != nil && ok { - _, err := caveatReader.ReadCaveatByName(update.Relationship.OptionalCaveat.CaveatName) - if errors.As(err, &datastore.ErrCaveatNameNotFound{}) { + if update.Relationship.OptionalCaveat != nil && update.Relationship.OptionalCaveat.CaveatName != "" { + if caveatReader, ok := rwt.(datastore.CaveatReader); ok { + _, err := caveatReader.ReadCaveatByName(update.Relationship.OptionalCaveat.CaveatName) + if errors.As(err, &datastore.ErrCaveatNameNotFound{}) { + return status.Errorf( + codes.FailedPrecondition, + "the caveat `%s` was not found for relationship `%s`", + update.Relationship.OptionalCaveat.CaveatName, + tuple.StringRelationship(update.Relationship), + ) + } + + if err != nil { + return rewritePermissionsError(ctx, err) + } + } else { return status.Errorf( codes.FailedPrecondition, - "the caveat `%s` was not found for relationship `%s`", - update.Relationship.OptionalCaveat.CaveatName, + "caveats are currently not supported for this datastore; found for relationship `%s`", tuple.StringRelationship(update.Relationship), ) } diff --git a/pkg/cmd/datastore/datastore.go b/pkg/cmd/datastore/datastore.go index dbf6c544e8..818db19c6e 100644 --- a/pkg/cmd/datastore/datastore.go +++ b/pkg/cmd/datastore/datastore.go @@ -85,6 +85,9 @@ type Config struct { // Internal WatchBufferLength uint16 + + // Experiments + ExperimentEnableCaveats bool } // RegisterDatastoreFlags adds datastore flags to a cobra command @@ -118,6 +121,11 @@ func RegisterDatastoreFlags(cmd *cobra.Command, opts *Config) { cmd.Flags().StringVar(&opts.SpannerEmulatorHost, "datastore-spanner-emulator-host", "", "URI of spanner emulator instance used for development and testing (e.g. localhost:9010)") cmd.Flags().StringVar(&opts.TablePrefix, "datastore-mysql-table-prefix", "", "prefix to add to the name of all SpiceDB database tables") + cmd.Flags().BoolVar(&opts.ExperimentEnableCaveats, "experiment-enable-caveats", false, "if true, experimental support for caveats is enabled; note that these are not fully implemented and may break") + if err := cmd.Flags().MarkDeprecated("experiment-enable-caveats", "this is an experiment"); err != nil { + panic("failed to mark flag deprecated: " + err.Error()) + } + // disabling stats is only for tests cmd.Flags().BoolVar(&opts.DisableStats, "datastore-disable-stats", false, "disable recording relationship counts to the stats table") if err := cmd.Flags().MarkHidden("datastore-disable-stats"); err != nil { @@ -294,5 +302,5 @@ func newMySQLDatastore(opts Config) (datastore.Datastore, error) { func newMemoryDatstore(opts Config) (datastore.Datastore, error) { log.Warn().Msg("in-memory datastore is not persistent and not feasible to run in a high availability fashion") - return memdb.NewMemdbDatastore(opts.WatchBufferLength, opts.RevisionQuantization, opts.GCWindow) + return memdb.NewMemdbDatastoreWithCaveatsOption(opts.WatchBufferLength, opts.RevisionQuantization, opts.GCWindow, opts.ExperimentEnableCaveats) } diff --git a/pkg/cmd/datastore/zz_generated.options.go b/pkg/cmd/datastore/zz_generated.options.go index 29962cd101..5f0c97716c 100644 --- a/pkg/cmd/datastore/zz_generated.options.go +++ b/pkg/cmd/datastore/zz_generated.options.go @@ -47,6 +47,7 @@ func (c *Config) ToOption() ConfigOption { to.SpannerEmulatorHost = c.SpannerEmulatorHost to.TablePrefix = c.TablePrefix to.WatchBufferLength = c.WatchBufferLength + to.ExperimentEnableCaveats = c.ExperimentEnableCaveats } } @@ -274,3 +275,10 @@ func WithWatchBufferLength(watchBufferLength uint16) ConfigOption { c.WatchBufferLength = watchBufferLength } } + +// WithExperimentEnableCaveats returns an option that can set ExperimentEnableCaveats on a Config +func WithExperimentEnableCaveats(experimentEnableCaveats bool) ConfigOption { + return func(c *Config) { + c.ExperimentEnableCaveats = experimentEnableCaveats + } +}