mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-25 13:59:08 +01:00
proxy: allow overriding tunnel-cache TTL via env var
The tunnel-peer validation cache (peer -> user/groups from ValidateTunnelPeer) memoizes positive results for a fixed 5 minutes, so an authorization change (e.g. a revoked peer) can take up to 5 minutes to take effect on the proxy. Add NB_PROXY_TUNNEL_CACHE_TTL to override the default at construction. The value is a Go duration string (e.g. "30s", "2m"); unset, unparseable, or non-positive values keep the 300s default and log a warning. Operators who need faster propagation can lower it (trading more ValidateTunnelPeer RPCs for freshness).
This commit is contained in:
@@ -3,20 +3,30 @@ package auth
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/sync/singleflight"
|
"golang.org/x/sync/singleflight"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/proxy/internal/types"
|
"github.com/netbirdio/netbird/proxy/internal/types"
|
||||||
"github.com/netbirdio/netbird/shared/management/proto"
|
"github.com/netbirdio/netbird/shared/management/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// tunnelCacheTTL caps how long a positive ValidateTunnelPeer result is
|
// tunnelCacheTTL is the default cap on how long a positive ValidateTunnelPeer
|
||||||
// reused before re-fetching from management. 5 minutes balances freshness
|
// result is reused before re-fetching from management. 5 minutes balances
|
||||||
// against management load on busy mesh networks.
|
// freshness against management load on busy mesh networks. Override it with
|
||||||
|
// envTunnelCacheTTL when an account needs authorization changes to take effect
|
||||||
|
// sooner (at the cost of more ValidateTunnelPeer RPCs).
|
||||||
const tunnelCacheTTL = 300 * time.Second
|
const tunnelCacheTTL = 300 * time.Second
|
||||||
|
|
||||||
|
// envTunnelCacheTTL overrides tunnelCacheTTL. The value is a Go duration string
|
||||||
|
// (e.g. "30s", "2m"); an unset, unparseable, or non-positive value keeps the
|
||||||
|
// default.
|
||||||
|
const envTunnelCacheTTL = "NB_PROXY_TUNNEL_CACHE_TTL"
|
||||||
|
|
||||||
// tunnelCachePerAccount caps the number of cached identities per account.
|
// tunnelCachePerAccount caps the number of cached identities per account.
|
||||||
// Bounded eviction avoids memory growth in pathological cases (huge peer
|
// Bounded eviction avoids memory growth in pathological cases (huge peer
|
||||||
// roster, brief request bursts) while staying generous for normal use.
|
// roster, brief request bursts) while staying generous for normal use.
|
||||||
@@ -60,16 +70,35 @@ type accountBucket struct {
|
|||||||
order []tunnelCacheKey
|
order []tunnelCacheKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// newTunnelValidationCache constructs a cache with default TTL and bounds.
|
// newTunnelValidationCache constructs a cache with the configured TTL
|
||||||
|
// (envTunnelCacheTTL override or default) and default bounds.
|
||||||
func newTunnelValidationCache() *tunnelValidationCache {
|
func newTunnelValidationCache() *tunnelValidationCache {
|
||||||
return &tunnelValidationCache{
|
return &tunnelValidationCache{
|
||||||
entries: make(map[types.AccountID]*accountBucket),
|
entries: make(map[types.AccountID]*accountBucket),
|
||||||
ttl: tunnelCacheTTL,
|
ttl: tunnelCacheTTLFromEnv(),
|
||||||
maxSize: tunnelCachePerAccount,
|
maxSize: tunnelCachePerAccount,
|
||||||
now: time.Now,
|
now: time.Now,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tunnelCacheTTLFromEnv returns the tunnel-cache TTL, honoring the
|
||||||
|
// envTunnelCacheTTL override. The override must be a positive Go duration
|
||||||
|
// string (e.g. "30s", "2m"); anything unset, unparseable, or non-positive
|
||||||
|
// falls back to tunnelCacheTTL.
|
||||||
|
func tunnelCacheTTLFromEnv() time.Duration {
|
||||||
|
raw := strings.TrimSpace(os.Getenv(envTunnelCacheTTL))
|
||||||
|
if raw == "" {
|
||||||
|
return tunnelCacheTTL
|
||||||
|
}
|
||||||
|
d, err := time.ParseDuration(raw)
|
||||||
|
if err != nil || d <= 0 {
|
||||||
|
log.Warnf("ignoring invalid %s=%q (want a positive Go duration like 30s or 2m); using default %s",
|
||||||
|
envTunnelCacheTTL, raw, tunnelCacheTTL)
|
||||||
|
return tunnelCacheTTL
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
// get returns a cached response for the key, or nil when missing or
|
// get returns a cached response for the key, or nil when missing or
|
||||||
// expired. Expired entries are evicted lazily on read.
|
// expired. Expired entries are evicted lazily on read.
|
||||||
func (c *tunnelValidationCache) get(key tunnelCacheKey) *proto.ValidateTunnelPeerResponse {
|
func (c *tunnelValidationCache) get(key tunnelCacheKey) *proto.ValidateTunnelPeerResponse {
|
||||||
|
|||||||
@@ -169,3 +169,32 @@ func TestTunnelCache_BoundedSizeEvictsOldest(t *testing.T) {
|
|||||||
assert.NotNil(t, cache.get(keys[1]), "second-newest must remain cached")
|
assert.NotNil(t, cache.get(keys[1]), "second-newest must remain cached")
|
||||||
assert.NotNil(t, cache.get(keys[2]), "newest must remain cached")
|
assert.NotNil(t, cache.get(keys[2]), "newest must remain cached")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTunnelCacheTTLFromEnv(t *testing.T) {
|
||||||
|
t.Run("unset uses default", func(t *testing.T) {
|
||||||
|
t.Setenv(envTunnelCacheTTL, "")
|
||||||
|
assert.Equal(t, tunnelCacheTTL, tunnelCacheTTLFromEnv())
|
||||||
|
})
|
||||||
|
t.Run("valid duration overrides", func(t *testing.T) {
|
||||||
|
t.Setenv(envTunnelCacheTTL, "45s")
|
||||||
|
assert.Equal(t, 45*time.Second, tunnelCacheTTLFromEnv())
|
||||||
|
})
|
||||||
|
t.Run("whitespace trimmed", func(t *testing.T) {
|
||||||
|
t.Setenv(envTunnelCacheTTL, " 2m ")
|
||||||
|
assert.Equal(t, 2*time.Minute, tunnelCacheTTLFromEnv())
|
||||||
|
})
|
||||||
|
t.Run("unparseable uses default", func(t *testing.T) {
|
||||||
|
t.Setenv(envTunnelCacheTTL, "nonsense")
|
||||||
|
assert.Equal(t, tunnelCacheTTL, tunnelCacheTTLFromEnv())
|
||||||
|
})
|
||||||
|
t.Run("non-positive uses default", func(t *testing.T) {
|
||||||
|
t.Setenv(envTunnelCacheTTL, "0s")
|
||||||
|
assert.Equal(t, tunnelCacheTTL, tunnelCacheTTLFromEnv())
|
||||||
|
t.Setenv(envTunnelCacheTTL, "-30s")
|
||||||
|
assert.Equal(t, tunnelCacheTTL, tunnelCacheTTLFromEnv())
|
||||||
|
})
|
||||||
|
t.Run("constructor honors override", func(t *testing.T) {
|
||||||
|
t.Setenv(envTunnelCacheTTL, "90s")
|
||||||
|
assert.Equal(t, 90*time.Second, newTunnelValidationCache().ttl)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user