{"record":{"id":"18f07ee5b6c83e20","repo":"juanfont/headscale","slug":"nodeattrs-ippool-requires-the-ip-allocator-https","errorCode":null,"errorMessage":"nodeAttrs ipPool requires the IP allocator (https://github.com/juanfont/headscale/issues/2912)","messagePattern":"nodeAttrs ipPool requires the IP allocator \\(https://github\\.com/juanfont/headscale/issues/2912\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/policy/v2/types.go","lineNumber":92,"sourceCode":"// Grant validation errors.\nvar (\n\tErrGrantMissingIPOrApp             = errors.New(\"ip and app can not both be empty\")\n\tErrGrantViaNotATag                 = errors.New(\"via can only be a tag\")\n\tErrProtocolPortInvalidFormat       = errors.New(\"expected only one colon in Internet protocol and port type\")\n\tErrCapNameInvalidForm              = errors.New(\"capability name must have the form {domain}/{path}\")\n\tErrCapNameTailscaleDomain          = errors.New(\"capability name must not be in the tailscale.com domain\")\n\tErrGrantAutogroupSelfInvalidSource = errors.New(\"autogroup:self can only be used with users, groups, or supported autogroups\")\n\tErrGrantAppWithAutogroupInternet   = errors.New(\"cannot use app grants with autogroup:internet\")\n\tErrGrantDefaultRouteCIDR           = errors.New(\"to allow all IP addresses, use \\\"*\\\" or \\\"autogroup:internet\\\"\")\n)\n\n// NodeAttrs validation errors.\nvar (\n\tErrNodeAttrsIPPoolReserved      = errors.New(\"nodeAttrs ipPool must not overlap reserved Tailscale ranges\")\n\tErrNodeAttrsIPPoolOutOfRange    = errors.New(\"nodeAttrs ipPool must be within 100.64.0.0/10\")\n\tErrNodeAttrsAutogroupNotAllowed = errors.New(\"nodeAttrs target does not support this autogroup\")\n\tErrNodeAttrUnsupported          = errors.New(\"nodeAttrs uses a feature headscale does not yet support\")\n\tErrNodeAttrIPPoolUnsupported    = errors.New(\"nodeAttrs ipPool requires the IP allocator (https://github.com/juanfont/headscale/issues/2912)\")\n\tErrNodeAttrTargetUnsupported    = errors.New(\"nodeAttrs target alias type is not supported\")\n)\n\n// nodeAttrUnsupportedCaps lists caps that headscale parses but cannot act on\n// today. Each entry maps to the tracking issue an operator can follow. The\n// caps are accepted by Tailscale SaaS, but delivering them via headscale\n// without the matching server-side machinery would be misleading — nodes\n// would advertise a feature that does not work. Reject at policy load and\n// point operators at the issue.\nvar nodeAttrUnsupportedCaps = map[tailcfg.NodeCapability]string{\n\ttailcfg.NodeAttrFunnel: \"https://github.com/juanfont/headscale/issues/2527\",\n}\n\n// Policy validation errors.\nvar (\n\tErrInvalidUsername             = errors.New(\"username must contain @\")\n\tErrUserNotFound                = errors.New(\"user not found\")\n\tErrMultipleUsersFound          = errors.New(\"multiple users found\")","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/policy/v2/types.go#L74-L110","documentation":"Returned unconditionally at hscontrol/policy/v2/types.go:2718-2719 whenever a nodeAttrs entry has a non-empty ipPool list. The ipPool feature depends on an IP allocator that headscale has not implemented (tracked at https://github.com/juanfont/headscale/issues/2912), so any ipPool key is rejected at policy load regardless of whether the prefixes themselves are valid.","triggerScenarios":"Any policy with {\"nodeAttrs\": [{\"ipPool\": [\"100.100.1.0/24\"]}]} (any CIDR, even a valid in-range one) fails validation; the same entry may additionally produce range errors, but the unsupported-feature error fires first because the feature does not exist yet.","commonSituations":"Copying a Tailscale SaaS policy that provisions per-node IP pools; testing whether ipPool silently no-ops and discovering it hard-fails; upgrading headscale and having a previously unimplemented-but-ignored key now rejected.","solutions":["Delete the ipPool key from every nodeAttrs block","Track issue #2912 for IP allocator support","If you need deterministic node addressing, rely on existing prefix allocation config outside policy ipPool"],"exampleFix":"// before\n{\"nodeAttrs\": [{\"target\": [\"autogroup:member\"], \"ipPool\": [\"100.80.0.0/16\"]}]}\n// after\n{\"nodeAttrs\": [{\"target\": [\"autogroup:member\"]}]}","handlingStrategy":"validation","validationCode":"for _, na := range policy.NodeAttrs {\n    if len(na.IPPool) > 0 {\n        return errors.New(\"nodeAttrs ipPool is not supported by headscale yet\")\n    }\n}","typeGuard":"null","tryCatchPattern":"if errors.Is(err, policy.ErrNodeAttrIPPoolUnsupported) {\n    // remove ipPool blocks; feature pending issue #2912\n}","preventionTips":["Never emit ipPool in generated policies until #2912 ships","Strip ipPool keys when importing SaaS policies"],"tags":["headscale","policy","nodeattrs","ippool","unsupported-feature"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}