{"record":{"id":"21100d3af7bf82b1","repo":"slackhq/nebula","slug":"s-rule-v-s","errorCode":null,"errorMessage":"%s rule #%v; %s","messagePattern":"(.+?) rule #(.+?); (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"firewall.go","lineNumber":341,"sourceCode":"\t\ttable = \"firewall.inbound\"\n\t} else {\n\t\ttable = \"firewall.outbound\"\n\t}\n\n\tr := c.Get(table)\n\tif r == nil {\n\t\treturn nil\n\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}","sourceCodeStart":323,"sourceCodeEnd":359,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/firewall.go#L323-L359","documentation":"While iterating rules in a firewall table, AddFirewallRulesFromConfig calls convertRule to decode each entry into a firewall rule. If convertRule fails (bad types, missing fields, unparseable values), the error is wrapped as \"<table> rule #<index>; <detail>\" so the offending rule can be located in the config.","triggerScenarios":"Any rule object in inbound/outbound whose fields cannot be converted — e.g. \"port: [80]\" as a list instead of a string, non-string ca_sha, or a rule entry that is not a map.","commonSituations":"YAML type coercion surprises (unquoted values that decode as ints/bools), typos in field structure, or programmatic config generation emitting wrong JSON/YAML types.","solutions":["Read the wrapped detail after \"rule #N;\" — it names the exact field and conversion failure.","Quote port, code, and ca_sha values as strings in YAML to avoid type coercion.","Ensure each rule entry is a mapping (key: value pairs), not a scalar or list."],"exampleFix":"// before (config)\n- port: [80, 443]\n  proto: tcp\n// after\n- port: 443\n  proto: tcp\n// (or two separate rules, one per port)","handlingStrategy":"validation","validationCode":"func precheckRules(table string, rules []map[string]any) error {\n    for i, r := range rules {\n        for k, v := range r {\n            switch k {\n            case \"port\", \"code\", \"ca_name\", \"ca_sha\", \"host\", \"proto\", \"cidr\", \"local_cidr\":\n                if _, ok := v.(string); !ok {\n                    return fmt.Errorf(\"%s rule #%d: field %q must be a quoted string, got %T\", table, i, k, v)\n                }\n            case \"groups\":\n                if _, ok := v.([]any); !ok && v != nil {\n                    return fmt.Errorf(\"%s rule #%d: groups must be a list\", table, i)\n                }\n            }\n        }\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := fw.AddFirewallRulesFromConfig(l, \"inbound\", rules); err != nil {\n    // err looks like \"inbound rule #2; ...\" — surface it with config file context\n    return fmt.Errorf(\"firewall config %s: %w\", configFile, err)\n}","preventionTips":["Quote all scalar rule values (port, code, ca_sha) in YAML.","Keep each rule a flat mapping with known keys only.","Test firewall config changes on one node before fleet rollout."],"tags":["firewall","yaml","configuration"],"backgroundTag":"invalid-config-shape","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"}