Add a new inter-query value cache to cache data across queries

This commit adds a new inter-query value cache that built-in
functions can use to cache information across queries.
For example, the `regex` and `glob` builtins can use this
to cache compiled regex and glob match patterns respectively.

The number of entries in the cache can be configured via the OPA
config. By default there is no limit.

Fixes: #6908

Signed-off-by: Ashutosh Narkar <anarkar4387@gmail.com>
This commit is contained in:
Ashutosh Narkar
2024-09-04 17:53:48 -07:00
parent f492f96d80
commit 2c56293695
21 changed files with 1021 additions and 381 deletions
+9
View File
@@ -32,6 +32,7 @@ type Basic struct {
printHook print.Hook
enablePrintStatements bool
interQueryCache cache.InterQueryCache
interQueryValueCache cache.InterQueryValueCache
}
// Runtime returns an argument that sets the runtime on the authorizer.
@@ -73,6 +74,13 @@ func InterQueryCache(interQueryCache cache.InterQueryCache) func(*Basic) {
}
}
// InterQueryValueCache enables the inter-query value cache on the authorizer
func InterQueryValueCache(interQueryValueCache cache.InterQueryValueCache) func(*Basic) {
return func(b *Basic) {
b.interQueryValueCache = interQueryValueCache
}
}
// NewBasic returns a new Basic object.
func NewBasic(inner http.Handler, compiler func() *ast.Compiler, store storage.Store, opts ...func(*Basic)) http.Handler {
b := &Basic{
@@ -107,6 +115,7 @@ func (h *Basic) ServeHTTP(w http.ResponseWriter, r *http.Request) {
rego.EnablePrintStatements(h.enablePrintStatements),
rego.PrintHook(h.printHook),
rego.InterQueryBuiltinCache(h.interQueryCache),
rego.InterQueryBuiltinValueCache(h.interQueryValueCache),
)
rs, err := rego.Eval(r.Context())
+38
View File
@@ -6,6 +6,7 @@ package authorizer
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
@@ -516,6 +517,43 @@ func TestInterQueryCache(t *testing.T) {
}
}
func TestInterQueryValueCache(t *testing.T) {
compiler := func() *ast.Compiler {
module := `
package system.authz
import rego.v1
allow if {
regex.match("foo.*", "foobar")
}`
c := ast.NewCompiler()
c.Compile(map[string]*ast.Module{
"test.rego": ast.MustParseModule(module),
})
if c.Failed() {
t.Fatalf("Unexpected error compiling test module: %v", c.Errors)
}
return c
}
recorder := httptest.NewRecorder()
req, err := http.NewRequest(http.MethodGet, "http://localhost:8181/v1/data", nil)
if err != nil {
t.Fatal(err)
}
config, _ := cache.ParseCachingConfig(nil)
interQueryValueCache := cache.NewInterQueryValueCache(context.Background(), config)
basic := NewBasic(&mockHandler{}, compiler, inmem.New(), InterQueryValueCache(interQueryValueCache), Decision(func() ast.Ref {
return ast.MustParseRef("data.system.authz.allow")
}))
// Execute the policy
basic.ServeHTTP(recorder, req)
}
func Equal(a, b []string) bool {
if len(a) != len(b) {
return false
+51 -38
View File
@@ -111,42 +111,43 @@ type Server struct {
Handler http.Handler
DiagnosticHandler http.Handler
router *mux.Router
addrs []string
diagAddrs []string
h2cEnabled bool
authentication AuthenticationScheme
authorization AuthorizationScheme
cert *tls.Certificate
tlsConfigMtx sync.RWMutex
certFile string
certFileHash []byte
certKeyFile string
certKeyFileHash []byte
certRefresh time.Duration
certPool *x509.CertPool
certPoolFile string
certPoolFileHash []byte
minTLSVersion uint16
mtx sync.RWMutex
partials map[string]rego.PartialResult
preparedEvalQueries *cache
store storage.Store
manager *plugins.Manager
decisionIDFactory func() string
logger func(context.Context, *Info) error
errLimit int
pprofEnabled bool
runtime *ast.Term
httpListeners []httpListener
metrics Metrics
defaultDecisionPath string
interQueryBuiltinCache iCache.InterQueryCache
allPluginsOkOnce bool
distributedTracingOpts tracing.Options
ndbCacheEnabled bool
unixSocketPerm *string
cipherSuites *[]uint16
router *mux.Router
addrs []string
diagAddrs []string
h2cEnabled bool
authentication AuthenticationScheme
authorization AuthorizationScheme
cert *tls.Certificate
tlsConfigMtx sync.RWMutex
certFile string
certFileHash []byte
certKeyFile string
certKeyFileHash []byte
certRefresh time.Duration
certPool *x509.CertPool
certPoolFile string
certPoolFileHash []byte
minTLSVersion uint16
mtx sync.RWMutex
partials map[string]rego.PartialResult
preparedEvalQueries *cache
store storage.Store
manager *plugins.Manager
decisionIDFactory func() string
logger func(context.Context, *Info) error
errLimit int
pprofEnabled bool
runtime *ast.Term
httpListeners []httpListener
metrics Metrics
defaultDecisionPath string
interQueryBuiltinCache iCache.InterQueryCache
interQueryBuiltinValueCache iCache.InterQueryValueCache
allPluginsOkOnce bool
distributedTracingOpts tracing.Options
ndbCacheEnabled bool
unixSocketPerm *string
cipherSuites *[]uint16
}
// Metrics defines the interface that the server requires for recording HTTP
@@ -748,7 +749,8 @@ func (s *Server) initHandlerAuthz(handler http.Handler) http.Handler {
authorizer.Decision(s.manager.Config.DefaultAuthorizationDecisionRef),
authorizer.PrintHook(s.manager.PrintHook()),
authorizer.EnablePrintStatements(s.manager.EnablePrintStatements()),
authorizer.InterQueryCache(s.interQueryBuiltinCache))
authorizer.InterQueryCache(s.interQueryBuiltinCache),
authorizer.InterQueryValueCache(s.interQueryBuiltinValueCache))
if s.metrics != nil {
handler = s.instrumentHandler(handler.ServeHTTP, PromHandlerAPIAuthz)
@@ -800,7 +802,12 @@ func (s *Server) initRouters(ctx context.Context) {
diagRouter := mux.NewRouter()
// authorizer, if configured, needs the iCache to be set up already
s.interQueryBuiltinCache = iCache.NewInterQueryCacheWithContext(ctx, s.manager.InterQueryBuiltinCacheConfig())
cacheConfig := s.manager.InterQueryBuiltinCacheConfig()
s.interQueryBuiltinCache = iCache.NewInterQueryCacheWithContext(ctx, cacheConfig)
s.interQueryBuiltinValueCache = iCache.NewInterQueryValueCache(ctx, cacheConfig)
s.manager.RegisterCacheTrigger(s.updateCacheConfig)
// Add authorization handler. This must come BEFORE authentication handler
@@ -933,6 +940,7 @@ func (s *Server) execQuery(ctx context.Context, br bundleRevisions, txn storage.
rego.Runtime(s.runtime),
rego.UnsafeBuiltins(unsafeBuiltinsMap),
rego.InterQueryBuiltinCache(s.interQueryBuiltinCache),
rego.InterQueryBuiltinValueCache(s.interQueryBuiltinValueCache),
rego.PrintHook(s.manager.PrintHook()),
rego.EnablePrintStatements(s.manager.EnablePrintStatements()),
rego.DistributedTracingOpts(s.distributedTracingOpts),
@@ -1121,6 +1129,7 @@ func (s *Server) v0QueryPath(w http.ResponseWriter, r *http.Request, urlPath str
rego.EvalParsedInput(input),
rego.EvalMetrics(m),
rego.EvalInterQueryBuiltinCache(s.interQueryBuiltinCache),
rego.EvalInterQueryBuiltinValueCache(s.interQueryBuiltinValueCache),
rego.EvalNDBuiltinCache(ndbCache),
}
@@ -1402,6 +1411,7 @@ func (s *Server) v1CompilePost(w http.ResponseWriter, r *http.Request) {
rego.Runtime(s.runtime),
rego.UnsafeBuiltins(unsafeBuiltinsMap),
rego.InterQueryBuiltinCache(s.interQueryBuiltinCache),
rego.InterQueryBuiltinValueCache(s.interQueryBuiltinValueCache),
rego.PrintHook(s.manager.PrintHook()),
)
@@ -1541,6 +1551,7 @@ func (s *Server) v1DataGet(w http.ResponseWriter, r *http.Request) {
rego.EvalMetrics(m),
rego.EvalQueryTracer(buf),
rego.EvalInterQueryBuiltinCache(s.interQueryBuiltinCache),
rego.EvalInterQueryBuiltinValueCache(s.interQueryBuiltinValueCache),
rego.EvalInstrument(includeInstrumentation),
rego.EvalNDBuiltinCache(ndbCache),
}
@@ -1760,6 +1771,7 @@ func (s *Server) v1DataPost(w http.ResponseWriter, r *http.Request) {
rego.EvalMetrics(m),
rego.EvalQueryTracer(buf),
rego.EvalInterQueryBuiltinCache(s.interQueryBuiltinCache),
rego.EvalInterQueryBuiltinValueCache(s.interQueryBuiltinValueCache),
rego.EvalInstrument(includeInstrumentation),
rego.EvalNDBuiltinCache(ndbCache),
}
@@ -2655,6 +2667,7 @@ func isPathOwned(path, root []string) bool {
func (s *Server) updateCacheConfig(cacheConfig *iCache.Config) {
s.interQueryBuiltinCache.UpdateConfig(cacheConfig)
s.interQueryBuiltinValueCache.UpdateConfig(cacheConfig)
}
func (s *Server) updateNDCache(enabled bool) {