{"record":{"id":"60f35e228c988457","repo":"slackhq/nebula","slug":"s-rule-v-at-least-one-of-host-group-cidr-lo","errorCode":null,"errorMessage":"%s rule #%v; at least one of host, group, cidr, local_cidr, ca_name, or ca_sha must be provided","messagePattern":"(.+?) rule #(.+?); at least one of host, group, cidr, local_cidr, ca_name, or ca_sha must be provided","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"firewall.go","lineNumber":349,"sourceCode":"\t}\n\n\trs, ok := r.([]any)\n\tif !ok {\n\t\treturn fmt.Errorf(\"%s failed to parse, should be an array of rules\", table)\n\t}\n\n\tfor i, t := range rs {\n\t\tr, err := convertRule(l, t, table, i)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"%s rule #%v; %s\", table, i, err)\n\t\t}\n\n\t\tif r.Code != \"\" && r.Port != \"\" {\n\t\t\treturn fmt.Errorf(\"%s rule #%v; only one of port or code should be provided\", table, i)\n\t\t}\n\n\t\tif r.Host == \"\" && len(r.Groups) == 0 && r.Cidr == \"\" && r.LocalCidr == \"\" && r.CAName == \"\" && r.CASha == \"\" {\n\t\t\treturn fmt.Errorf(\"%s rule #%v; at least one of host, group, cidr, local_cidr, ca_name, or ca_sha must be provided\", table, i)\n\t\t}\n\n\t\tvar sPort, errPort string\n\t\tif r.Code != \"\" {\n\t\t\terrPort = \"code\"\n\t\t\tsPort = r.Code\n\t\t} else {\n\t\t\terrPort = \"port\"\n\t\t\tsPort = r.Port\n\t\t}\n\n\t\tvar proto uint8\n\t\tvar startPort, endPort int32\n\t\tswitch r.Proto {\n\t\tcase \"any\":\n\t\t\tproto = firewall.ProtoAny\n\t\t\tstartPort, endPort, err = parsePort(sPort)\n\t\tcase \"tcp\":","sourceCodeStart":331,"sourceCodeEnd":367,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/firewall.go#L331-L367","documentation":"Every firewall rule must select some target: a host, at least one group, a cidr, a local_cidr, a ca_name, or a ca_sha. AddFirewallRulesFromConfig rejects rules that specify none of these, because such a rule could never match a meaningful connection set.","triggerScenarios":"A rule with only port/proto (e.g. {port: \"443\", proto: \"tcp\"}) and no host, group, cidr, local_cidr, ca_name, or ca_sha field.","commonSituations":"Oversimplified example configs, deleting the group/cidr line while editing, or templating that leaves selector fields empty.","solutions":["Add a selector to the rule — typically \"group: <name>\" or \"cidr: <prefix>\".","For host-specific rules use \"host\", for CA-based trust use \"ca_name\" or \"ca_sha\".","Use \"cidr: 0.0.0.0/0\" (and/or \"local_cidr: any\") only if you truly intend an allow-all rule."],"exampleFix":"// before (config)\n- port: 443\n  proto: tcp\n// after\n- port: 443\n  proto: tcp\n  group: web-servers","handlingStrategy":"validation","validationCode":"func ruleHasSelector(rule map[string]any) bool {\n    for _, k := range []string{\"host\", \"group\", \"groups\", \"cidr\", \"local_cidr\", \"ca_name\", \"ca_sha\"} {\n        if v, ok := rule[k]; ok && v != nil && v != \"\" {\n            return true\n        }\n    }\n    return false\n}\n// before AddFirewallRulesFromConfig: ensure ruleHasSelector(r) for every rule","typeGuard":null,"tryCatchPattern":"if err := fw.AddFirewallRulesFromConfig(l, \"outbound\", rules); err != nil {\n    if strings.Contains(err.Error(), \"at least one of host, group\") {\n        log.Fatalf(\"every rule needs host/group/cidr/local_cidr/ca_name/ca_sha: %v\", err)\n    }\n    return err\n}","preventionTips":["Require a selector field in your rule template/snippet library.","Never strip selector fields when simplifying example configs.","Add a CI check that every firewall rule has at least one selector key."],"tags":["firewall","configuration","validation"],"backgroundTag":"conflicting-rule-fields","analyzedSha":"dd8f660c0ac37903ec4080ca4d3c861ba9342ceb","analyzedAt":"2026-09-03T11:13:55.444Z","contentChangedAt":"2026-09-03T11:13:55.444Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}