{"record":{"id":"443add904fe40221","repo":"slackhq/nebula","slug":"only-one-of-group-or-groups-should-be-defined-bot","errorCode":null,"errorMessage":"only one of group or groups should be defined, both provided","messagePattern":"only one of group or groups should be defined, both provided","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"firewall.go","lineNumber":1004,"sourceCode":"\t\tswitch reflect.TypeOf(rg).Kind() {\n\t\tcase reflect.Slice:\n\t\t\tv := reflect.ValueOf(rg)\n\t\t\tr.Groups = make([]string, v.Len())\n\t\t\tfor i := 0; i < v.Len(); i++ {\n\t\t\t\tr.Groups[i] = v.Index(i).Interface().(string)\n\t\t\t}\n\t\tcase reflect.String:\n\t\t\tr.Groups = []string{rg.(string)}\n\t\tdefault:\n\t\t\tr.Groups = []string{fmt.Sprintf(\"%v\", rg)}\n\t\t}\n\t}\n\n\t//flatten group vs groups\n\tif singleGroup != \"\" {\n\t\t// Check if we have both groups and group provided in the rule config\n\t\tif len(r.Groups) > 0 {\n\t\t\treturn r, fmt.Errorf(\"only one of group or groups should be defined, both provided\")\n\t\t}\n\t\tr.Groups = []string{singleGroup}\n\t}\n\n\treturn r, nil\n}\n\n// sanity returns an error if the rule would be evaluated in a way that would short-circuit a configured check on a wildcard value\n// rules are evaluated as \"port AND proto AND (ca_sha OR ca_name) AND (host OR group OR groups OR cidr) AND local_cidr\"\nfunc (r *rule) sanity() error {\n\t//port, proto, local_cidr are AND, no need to check here\n\t//ca_sha and ca_name don't have a wildcard value, no need to check here\n\tgroupsEmpty := len(r.Groups) == 0\n\thostEmpty := r.Host == \"\"\n\tcidrEmpty := r.Cidr == \"\"\n\n\tif (groupsEmpty && hostEmpty && cidrEmpty) == true {\n\t\treturn nil //no content!","sourceCodeStart":986,"sourceCodeEnd":1022,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/firewall.go#L986-L1022","documentation":"In firewall rule normalization, the config allows either the singular 'group' or plural 'groups' field. Providing both is ambiguous, so the rule is rejected with this error rather than merging them. This lives in the rule-translation step that converts YAML firewall rule structs into internal rules.","triggerScenarios":"A single firewall rule in the YAML/config struct defines both 'group: foo' and 'groups: [foo, bar]'.","commonSituations":"Merged config files where one fragment used group and another used groups; copy-paste from examples mixing syntax; templating that appends a singular group to a rule already carrying a list.","solutions":["Keep only the 'groups' list and move the singular value into it","Or keep only 'group' if a single group suffices","Search your config for rules containing both keys"],"exampleFix":"// before\ngroup: prod\ngroups:\n  - prod\n  - staging\n// after\ngroups:\n  - prod\n  - staging","handlingStrategy":"validation","validationCode":"func checkGroupConflict(r FirewallRuleConfig) error {\n    if r.Group != \"\" && len(r.Groups) > 0 {\n        return fmt.Errorf(\"rule defines both group and groups\")\n    }\n    return nil\n}","typeGuard":"func hasGroupConflict(group string, groups []string) bool {\n    return group != \"\" && len(groups) > 0\n}","tryCatchPattern":"if err := loadFirewallConfig(cfg); err != nil {\n    if strings.Contains(err.Error(), \"only one of group or groups\") {\n        return fmt.Errorf(\"fix rule config: %w\", err)\n    }\n    return err\n}","preventionTips":["Standardize on 'groups' lists in all configs","Grep configs for rules containing both group and groups keys","When merging config fragments, normalize singular group into groups first"],"tags":["go","firewall","config","validation"],"backgroundTag":"firewall-rule-invalid","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"}