crowdsecurity/crowdsec · error
keyring rotation interval %s is below the floor %s
Error message
keyring rotation interval %s is below the floor %s
What it means
NewKeyRing rejects a rotation interval below keyringMinRotation. Cookie-signing keys rotate on this interval; too-fast rotation would create keys faster than the maxLive window can meaningfully retain, breaking verification of recently issued cookies. The constructor fails fast instead of accepting an unusable rotation policy.
Source
Thrown at pkg/appsec/challenge/keyring.go:103
mu sync.RWMutex
cache map[int64][]byte // epoch -> per-epoch sign key
}
// log returns the component logger (never nil; see the logger field).
func (k *KeyRing) log() *log.Entry {
return k.logger
}
// NewKeyRing constructs a KeyRing. masterSecret must be at least minSecretBytes
// long (callers should already have validated this via WithMasterSecret); the
// rotation interval must be at least keyringMinRotation. maxLive defaults to
// keyringDefaultMaxLive when zero.
func NewKeyRing(masterSecret []byte, rotationInterval time.Duration, maxLive int) (*KeyRing, error) {
if len(masterSecret) < minSecretBytes {
return nil, fmt.Errorf("keyring master secret is %d bytes; minimum is %d", len(masterSecret), minSecretBytes)
}
if rotationInterval < keyringMinRotation {
return nil, fmt.Errorf("keyring rotation interval %s is below the floor %s", rotationInterval, keyringMinRotation)
}
if maxLive <= 0 {
maxLive = keyringDefaultMaxLive
}
return &KeyRing{
masterSecret: masterSecret,
rotationInterval: rotationInterval,
maxLive: maxLive,
clockSkew: keyringClockSkew,
masterCookieKey: deriveMasterCookieKey(masterSecret),
logger: log.StandardLogger().WithField("module", "challenge"),
now: time.Now,
cache: make(map[int64][]byte),
}, nil
}
// CurrentEpoch returns the epoch identifier for the current wall-clock time.View on GitHub (pinned to 909b515798)
Solutions
- Raise the rotation interval to at least keyringMinRotation (use the documented floor, e.g. minutes)
- Check the unit: a raw integer like 500 interpreted as nanoseconds is effectively zero — use time.Duration constants
- Leave the value unset so defaults apply if you do not need custom rotation
- Adjust tests to use a valid interval and manipulate time rather than shrinking the interval
Example fix
// before kr, err := challenge.NewKeyRing(secret, 500 * time.Millisecond, 0) // after kr, err := challenge.NewKeyRing(secret, 5 * time.Minute, 0) // >= keyringMinRotation
Defensive patterns
Strategy: validation
Validate before calling
if rotation < keyringMinRotation { rotation = defaultRotation }
kr, err := challenge.NewKeyRing(secret, rotation, maxLive) Prevention
- Always build durations with time.Minute / time.Hour constants, not raw integers
- Clamp user-supplied rotation values to the documented floor before calling NewKeyRing
- Leave rotation unset to accept the default
- Add a unit test covering the minimum accepted interval
When it happens
Trigger: Calling NewKeyRing with a rotationInterval smaller than keyringMinRotation (sub-minute/near-zero, e.g. time.Second or 0 via direct construction).
Common situations: A config value like `1s` or `0` supplied as the keyring rotation interval; a test constructing a KeyRing with an unrealistically small duration; unit confusion (milliseconds passed as a Duration value).
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- build challenge keyring: %w
- keyring master secret is %d bytes; minimum is %d
- missing lapi client credentials
- no appsec_config provided
- missing TLS key file
AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06).
Data as JSON: /api/errors/56a012977e3fd4ab.
Report an issue: GitHub.