cilium/cilium · critical

IPSec requires a valid BootID

Error message

IPSec requires a valid BootID

What it means

During InitLocalNode, after populating the local node from config and Kubernetes, the agent reads the node's BootID (derived from the machine boot / system information). If IPSec (transparent encryption) is enabled but the BootID is empty, initialization aborts, because IPSec key material is keyed per boot and a missing BootID would make SPI/XFRM state unsafe.

Source

Thrown at pkg/node/sync/local_node_sync.go:86

	old node.LocalNode
}

func (ini *localNodeSynchronizer) InitLocalNode(ctx context.Context, n *node.LocalNode) error {
	n.Source = source.Local

	if err := ini.initFromConfig(n); err != nil {
		return err
	}

	n.Local.UnderlayProtocol = ini.TunnelConfig.UnderlayProtocol()

	if err := ini.initFromK8s(ctx, n); err != nil {
		return err
	}

	n.BootID = node.GetBootID(ini.Logger)
	if ini.IPsecConfig.Enabled() && n.BootID == "" {
		return fmt.Errorf("IPSec requires a valid BootID")
	}

	for _, fn := range ini.ExtraInitFuncs {
		if err := fn(ctx, n); err != nil {
			return err
		}
	}

	return nil
}

func (ini *localNodeSynchronizer) SyncLocalNode(ctx context.Context, store *node.LocalNodeStore) {
	if ini.K8sLocalNode == nil {
		return
	}

	for ev := range ini.K8sLocalNode.Events(ctx) {
		if ev.Kind == resource.Upsert {

View on GitHub (pinned to ac7b90affa)

Solutions

  1. Verify /proc/sys/kernel/random/boot_id exists and is readable inside the agent's environment; mount it if the runtime hides it.
  2. Check node.GetBootID() fallback paths and logs to see why it returned empty (e.g. missing procfs mount).
  3. Run the agent with proper privileges/host mounts (hostPID/proc access) so kernel files are visible.
  4. If IPsec is not actually needed, disable transparent encryption (--enable-ipsec=false) to bypass the requirement.

Example fix

// before: container without boot_id visible
//   docker run cilium/...   (no /proc/sys/kernel/random/boot_id)
// after: expose host proc
   docker run -v /proc/sys/kernel/random/boot_id:/proc/sys/kernel/random/boot_id:ro cilium/...
Defensive patterns

Strategy: validation

Validate before calling

bootID, err := os.ReadFile("/proc/sys/kernel/random/boot_id")
if err != nil || len(strings.TrimSpace(string(bootID))) == 0 {
    return errors.New("boot id unavailable: IPsec cannot be enabled")
}
if ipsecEnabled && node.GetBootID(logger) == "" {
    return errors.New("IPSec requires a valid BootID")
}

Type guard

func hasBootID(n *node.LocalNode) bool {
    return n.BootID != ""
}

Try / catch

if err := synchronizer.InitLocalNode(ctx, n); err != nil {
    if strings.Contains(err.Error(), "BootID") {
        log.Error("IPsec enabled but boot ID unavailable; aborting with encryption disabled rather than unsafe state")
    }
    return err
}

Prevention

When it happens

Trigger: IPsecConfig.Enabled() is true (encryption enabled, e.g. --enable-ipsec=true) and node.GetBootID() returns an empty string during local node initialization, causing InitLocalNode (the public LocalNodeSynchronizer entry point) to return this error and agent startup to fail.

Common situations: Containers or VMs lacking /proc/sys/kernel/random/boot_id or equivalent boot identification; running in environments (some sandboxed/container runtimes) where the boot ID file is unreadable; unusual platforms where GetBootID cannot determine a stable boot identifier.

Related errors


AI-assisted analysis of cilium/cilium@ac7b90affa (2026-08-31). Data as JSON: /api/errors/028feedf8006f949. Report an issue: GitHub.