crowdsecurity/crowdsec · critical

generate random master secret: %w

Error message

generate random master secret: %w

What it means

generateRandomSecret wraps a failure from crypto/rand.Read when generating the default 32-byte master secret (used when no master_secret is configured). crypto/rand essentially never fails on normal systems; failure indicates the OS entropy source is unavailable, so no secure key can be produced and startup must abort.

Source

Thrown at pkg/appsec/challenge/secret.go:28

	"encoding/hex"
	"errors"
	"fmt"
)

// minSecretBytes is the smallest accepted master-secret length. 32 bytes is
// the natural minimum for an HMAC-SHA256 key with full security margin; we
// reject anything shorter as a configuration error rather than silently
// padding it.
const minSecretBytes = 32

// generateRandomSecret returns a fresh 32-byte secret suitable for use when
// no master_secret is configured. Single-instance deployments are fine with
// this; distributed deployments MUST configure a shared secret because each
// instance generates an independent random one here.
func generateRandomSecret() ([]byte, error) {
	buf := make([]byte, 32)
	if _, err := crand.Read(buf); err != nil {
		return nil, fmt.Errorf("generate random master secret: %w", err)
	}
	return buf, nil
}

// ParseConfiguredSecret accepts a configured master secret as either a hex
// string (preferred — encodes raw bytes unambiguously) or a raw passphrase
// (fallback for human-edited configs). The result is at least minSecretBytes.
func ParseConfiguredSecret(value string) ([]byte, error) {
	if value == "" {
		return nil, errors.New("empty master secret")
	}

	// Hex form: even length, hex digits only.
	if isHex(value) {
		raw, err := hex.DecodeString(value)
		if err == nil {
			if len(raw) < minSecretBytes {
				return nil, fmt.Errorf("hex master secret decodes to %d bytes; minimum is %d", len(raw), minSecretBytes)

View on GitHub (pinned to 909b515798)

Solutions

  1. Fix the host entropy source: ensure /dev/urandom exists and getrandom(2) is not blocked by seccomp/AppArmor
  2. Configure an explicit master_secret so the random generator path is not used at all
  3. Check container runtime settings — run with a normal /dev mount rather than a stripped one
  4. Retry startup; if the failure is transient (early-boot entropy), it may succeed once the system is up

Example fix

// before: no secret configured, relying on random generation
opts := challenge.BuildOptions(cfg)
// after: explicit secret avoids the failing path
secret, _ := challenge.ParseConfiguredSecret("<64-hex-chars>")
opts := challenge.BuildOptions(cfg, challenge.WithMasterSecret(secret))
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check is not practical; ensure host entropy availability and/or configure a secret
if os.Getenv("CS_CHALLENGE_MASTER_SECRET") == "" { log.Warn("no master_secret configured; using random (single-instance only)") }

Try / catch

secret, err := challenge.ParseConfiguredSecret(cfgValue)
if err != nil {
    secret, err = generateSecret() // surfaces the wrapped crypto/rand failure
    if err != nil { return fmt.Errorf("challenge runtime init: %w", err) }
}

Prevention

When it happens

Trigger: NewChallengeRuntime is built without a configured master_secret and crypto/rand.Read fails — e.g. a container/sandbox where /dev/urandom or getrandom(2) is unavailable or blocked.

Common situations: Restricted containers or seccomp profiles blocking getrandom; embedded/minimal systems lacking an entropy device; heavily sandboxed CI environments.

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


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