Add config option to disable named inter-query built-in cache (#8287)

Signed-off-by: Sebastian Spaink <sebastianspaink@gmail.com>
This commit is contained in:
Sebastian Spaink
2026-02-04 11:11:59 -06:00
committed by GitHub
parent 42320981ff
commit d4e20cc7b0
5 changed files with 200 additions and 19 deletions
+2
View File
@@ -924,7 +924,9 @@ that requires GraphQL schemas.
| `caching.inter_query_builtin_cache.stale_entry_eviction_period_seconds` | `int64` | No | Stale entry eviction period in seconds. OPA will drop expired items from the cache every `stale_entry_eviction_period_seconds`. By default, set to `0` indicating stale entry eviction is disabled. |
| `caching.inter_query_builtin_value_cache.max_num_entries` | `int` | No | Maximum number of entries in the Inter-query value cache. OPA will drop random items from the cache if this limit is exceeded. By default, set to `0` indicating unlimited size. |
| `caching.inter_query_builtin_value_cache.named.io_jwt.max_num_entries` | `int` | No | Maximum number of entries in the `io_jwt` cache, used by the [`io.jwt` token verification](./policy-reference/builtins/tokens) built-in functions. OPA will drop random items from the cache if this limit is exceeded. By default, this cache is disabled. |
| `caching.inter_query_builtin_value_cache.named.io_jwt.disabled` | `bool` | No | Explicitly disable `io_jwt`, by default this is `true`. Setting this to `false` will enable `io_jwt` and set `max_num_entries` to `0` unless configured otherwise. |
| `caching.inter_query_builtin_value_cache.named.graphql.max_num_entries` | `int` | No | Maximum number of entries in the `graphql` cache, used by the [`graphql` builtins](./policy-reference/builtins/graphql) built-in functions to cache parsed schemas. OPA will drop random items from the cache if this limit is exceeded. By default, this cache is set to a maximum of 10 entries. |
| `caching.inter_query_builtin_value_cache.named.graphql.disabled` | `bool` | No | Explicitly disable `graphql`, by default this is `false`. Setting this to `true` will disable `graphql`. |
## Distributed tracing
+20 -5
View File
@@ -32,7 +32,7 @@ func getDefaultInterQueryBuiltinValueCacheConfig(name string) *NamedValueCacheCo
// RegisterDefaultInterQueryBuiltinValueCacheConfig registers a default configuration for the inter-query value cache;
// used when none has been explicitly configured.
// To disable a named cache when not configured, pass a nil config.
// To disable a named cache when not configured, pass a config with the disabled value set to true.
func RegisterDefaultInterQueryBuiltinValueCacheConfig(name string, config *NamedValueCacheConfig) {
interQueryBuiltinValueCacheDefaultConfigs[name] = config
}
@@ -58,7 +58,8 @@ func (c *Config) Clone() *Config {
// NamedValueCacheConfig represents the configuration of a named cache that built-in functions can utilize.
// A default configuration to be used if not explicitly configured can be registered using RegisterDefaultInterQueryBuiltinValueCacheConfig.
type NamedValueCacheConfig struct {
MaxNumEntries *int `json:"max_num_entries,omitempty"`
MaxNumEntries *int `json:"max_num_entries,omitempty"`
Disabled *bool `json:"disabled,omitempty"`
}
// Clone creates a deep copy of NamedValueCacheConfig.
@@ -73,6 +74,10 @@ func (n *NamedValueCacheConfig) Clone() *NamedValueCacheConfig {
maxEntries := *n.MaxNumEntries
clone.MaxNumEntries = &maxEntries
}
if n.Disabled != nil {
disabled := *n.Disabled
clone.Disabled = &disabled
}
return clone
}
@@ -220,9 +225,15 @@ func (c *Config) validateAndInjectDefaults() error {
}
for name, namedConfig := range c.InterQueryBuiltinValueCache.NamedCacheConfigs {
numEntries := *namedConfig.MaxNumEntries
if numEntries < 0 {
return fmt.Errorf("invalid max_num_entries %v for named cache %v", numEntries, name)
if namedConfig == nil || (namedConfig.MaxNumEntries == nil && namedConfig.Disabled == nil) {
return fmt.Errorf("missing configuration for named cache %v", name)
}
if namedConfig.MaxNumEntries != nil {
numEntries := *namedConfig.MaxNumEntries
if numEntries < 0 {
return fmt.Errorf("invalid max_num_entries %v for named cache %v", numEntries, name)
}
}
}
@@ -605,6 +616,10 @@ func (c *interQueryBuiltinValueCache) GetCache(name string) InterQueryValueCache
return nil
}
if config.Disabled != nil && *config.Disabled {
return nil
}
nc = &interQueryValueCacheBucket{
items: *newItemsMap(),
config: config,
+168 -13
View File
@@ -27,7 +27,7 @@ func TestParseCachingConfig(t *testing.T) {
maxNumEntriesInterQueryValueCache := new(int)
*maxNumEntriesInterQueryValueCache = defaultInterQueryBuiltinValueCacheSize
expected := &Config{
defaultExpected := &Config{
InterQueryBuiltinCache: InterQueryBuiltinCacheConfig{
MaxSizeBytes: maxSize,
StaleEntryEvictionPeriodSeconds: period,
@@ -39,20 +39,64 @@ func TestParseCachingConfig(t *testing.T) {
}
tests := map[string]struct {
input []byte
wantErr bool
input []byte
wantErr bool
expectedConfig *Config
}{
"empty_config": {
input: nil,
input: nil,
wantErr: false,
expectedConfig: defaultExpected,
},
"invalid_config_missing_configuration": {
input: []byte(`inter_query_builtin_value_cache:
named:
io_jwt:`),
wantErr: true,
},
"invalid_config_missing_values": {
input: []byte(`inter_query_builtin_value_cache:
named:
io_jwt:
disabled:
max_num_entries: `),
wantErr: true,
},
"valid_config_using_disabled": {
input: []byte(`inter_query_builtin_value_cache:
named:
io_jwt:
disabled: true`),
wantErr: false,
expectedConfig: func() *Config {
disabled := true
n := NamedValueCacheConfig{
Disabled: &disabled,
}
return &Config{
InterQueryBuiltinCache: InterQueryBuiltinCacheConfig{
MaxSizeBytes: maxSize,
StaleEntryEvictionPeriodSeconds: period,
ForcedEvictionThresholdPercentage: threshold,
},
InterQueryBuiltinValueCache: InterQueryBuiltinValueCacheConfig{
MaxNumEntries: maxNumEntriesInterQueryValueCache,
NamedCacheConfigs: map[string]*NamedValueCacheConfig{
"io_jwt": &n,
},
},
}
}(),
},
"default_limit": {
input: []byte(`{"inter_query_builtin_cache": {},}`),
wantErr: false,
input: []byte(`{"inter_query_builtin_cache": {},}`),
wantErr: false,
expectedConfig: defaultExpected,
},
"default_num_entries": {
input: []byte(`{"inter_query_builtin_value_cache": {},}`),
wantErr: false,
input: []byte(`{"inter_query_builtin_value_cache": {},}`),
wantErr: false,
expectedConfig: defaultExpected,
},
"bad_limit": {
input: []byte(`{"inter_query_builtin_cache": {"max_size_bytes": "100"},}`),
@@ -77,8 +121,8 @@ func TestParseCachingConfig(t *testing.T) {
}
}
if !tc.wantErr && !reflect.DeepEqual(config, expected) {
t.Fatalf("want %v got %v", expected, config)
if !tc.wantErr && !reflect.DeepEqual(config, tc.expectedConfig) {
t.Fatalf("want %v got %v", tc.expectedConfig, config)
}
})
}
@@ -92,13 +136,63 @@ func TestParseCachingConfig(t *testing.T) {
}
limit := int64(100)
expected.InterQueryBuiltinCache.MaxSizeBytes = &limit
defaultExpected.InterQueryBuiltinCache.MaxSizeBytes = &limit
if !reflect.DeepEqual(config, expected) {
t.Fatalf("want %v got %v", expected, config)
if !reflect.DeepEqual(config, defaultExpected) {
t.Fatalf("want %v got %v", defaultExpected, config)
}
}
func TestInterValueCache_DisabledConfig(t *testing.T) {
t.Run("config disabled", func(t *testing.T) {
// user configured `baz` to be disabled
disabled := true
cacheConfig := Config{
InterQueryBuiltinValueCache: InterQueryBuiltinValueCacheConfig{
NamedCacheConfigs: map[string]*NamedValueCacheConfig{
"baz": {
Disabled: &disabled,
},
},
},
}
// by default, it is enabled
var defaultDisabled bool
RegisterDefaultInterQueryBuiltinValueCacheConfig("baz", &NamedValueCacheConfig{
Disabled: &defaultDisabled,
})
c := NewInterQueryValueCache(t.Context(), &cacheConfig)
if c.GetCache("baz") != nil {
t.Fatal("Expected cache to be disabled")
}
})
t.Run("config enabled", func(t *testing.T) {
var disabled bool
cacheConfig := Config{
InterQueryBuiltinValueCache: InterQueryBuiltinValueCacheConfig{
NamedCacheConfigs: map[string]*NamedValueCacheConfig{
"baz": {
Disabled: &disabled,
},
},
},
}
defaultDisabled := true
RegisterDefaultInterQueryBuiltinValueCacheConfig("baz", &NamedValueCacheConfig{
Disabled: &defaultDisabled,
})
c := NewInterQueryValueCache(t.Context(), &cacheConfig)
if c.GetCache("baz") == nil {
t.Fatal("Expected cache to be enabled")
}
})
}
func TestInterValueCache_DefaultConfiguration(t *testing.T) {
t.Run("default config not set", func(t *testing.T) {
config := Config{
@@ -168,6 +262,67 @@ func TestInterValueCache_DefaultConfiguration(t *testing.T) {
func TestInterValueCache_NamedCaches(t *testing.T) {
t.Parallel()
t.Run("configured disable is respected", func(t *testing.T) {
disabled := true
config := Config{
InterQueryBuiltinValueCache: InterQueryBuiltinValueCacheConfig{
NamedCacheConfigs: map[string]*NamedValueCacheConfig{
"foo": {
Disabled: &disabled,
},
},
},
}
c := NewInterQueryValueCache(t.Context(), &config)
nc := c.GetCache("foo")
if nc != nil {
t.Fatalf("Expected cache to be disabled, but got %v", nc)
}
})
t.Run("configured enable is respected", func(t *testing.T) {
var disabled bool
config := Config{
InterQueryBuiltinValueCache: InterQueryBuiltinValueCacheConfig{
NamedCacheConfigs: map[string]*NamedValueCacheConfig{
"foo": {
Disabled: &disabled,
},
},
},
}
c := NewInterQueryValueCache(t.Context(), &config)
nc := c.GetCache("foo")
if nc == nil {
t.Fatalf("Expected cache to be enabled, but got %v", nc)
}
})
t.Run("Disable respected even if max_num_entries is specified", func(t *testing.T) {
disabled := true
config := Config{
InterQueryBuiltinValueCache: InterQueryBuiltinValueCacheConfig{
NamedCacheConfigs: map[string]*NamedValueCacheConfig{
"foo": {
MaxNumEntries: &[]int{5}[0],
Disabled: &disabled,
},
},
},
}
c := NewInterQueryValueCache(t.Context(), &config)
nc := c.GetCache("foo")
if nc != nil {
t.Fatalf("Expected cache to be disabled, but got %v", nc)
}
})
t.Run("configured max is respected", func(t *testing.T) {
config := Config{
InterQueryBuiltinValueCache: InterQueryBuiltinValueCacheConfig{
+5 -1
View File
@@ -1290,7 +1290,11 @@ func createTokenCacheKey(serializedJwt ast.Value, publicKey ast.Value) ast.Value
func init() {
// By default, the JWT cache is disabled.
cache.RegisterDefaultInterQueryBuiltinValueCacheConfig(tokenCacheName, nil)
disabled := true
var tokenCache = cache.NamedValueCacheConfig{
Disabled: &disabled,
}
cache.RegisterDefaultInterQueryBuiltinValueCacheConfig(tokenCacheName, &tokenCache)
RegisterBuiltinFunc(ast.JWTDecode.Name, builtinJWTDecode)
RegisterBuiltinFunc(ast.JWTVerifyRS256.Name, builtinJWTVerifyRS256)
+5
View File
@@ -79,6 +79,11 @@ func populateDefaultTypes(t *testing.T, fieldType reflect.Type, fieldValue refle
populateStruct(t, fieldType.Elem(), newStruct.Elem())
fieldValue.Set(newStruct)
return true
case reflect.Bool:
newBool := true
fieldValue.Set(reflect.ValueOf(&newBool))
return true
}