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

  1. Raise the rotation interval to at least keyringMinRotation (use the documented floor, e.g. minutes)
  2. Check the unit: a raw integer like 500 interpreted as nanoseconds is effectively zero — use time.Duration constants
  3. Leave the value unset so defaults apply if you do not need custom rotation
  4. 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

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


AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06). Data as JSON: /api/errors/56a012977e3fd4ab. Report an issue: GitHub.