slackhq/nebula · error

%s rule #%v; %s

Error message

%s rule #%v; %s

What it means

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.

Source

Thrown at firewall.go:341

		table = "firewall.inbound"
	} else {
		table = "firewall.outbound"
	}

	r := c.Get(table)
	if r == nil {
		return nil
	}

	rs, ok := r.([]any)
	if !ok {
		return fmt.Errorf("%s failed to parse, should be an array of rules", table)
	}

	for i, t := range rs {
		r, err := convertRule(l, t, table, i)
		if err != nil {
			return fmt.Errorf("%s rule #%v; %s", table, i, err)
		}

		if r.Code != "" && r.Port != "" {
			return fmt.Errorf("%s rule #%v; only one of port or code should be provided", table, i)
		}

		if r.Host == "" && len(r.Groups) == 0 && r.Cidr == "" && r.LocalCidr == "" && r.CAName == "" && r.CASha == "" {
			return fmt.Errorf("%s rule #%v; at least one of host, group, cidr, local_cidr, ca_name, or ca_sha must be provided", table, i)
		}

		var sPort, errPort string
		if r.Code != "" {
			errPort = "code"
			sPort = r.Code
		} else {
			errPort = "port"
			sPort = r.Port
		}

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Read the wrapped detail after "rule #N;" — it names the exact field and conversion failure.
  2. Quote port, code, and ca_sha values as strings in YAML to avoid type coercion.
  3. Ensure each rule entry is a mapping (key: value pairs), not a scalar or list.

Example fix

// before (config)
- port: [80, 443]
  proto: tcp
// after
- port: 443
  proto: tcp
// (or two separate rules, one per port)
Defensive patterns

Strategy: validation

Validate before calling

func precheckRules(table string, rules []map[string]any) error {
    for i, r := range rules {
        for k, v := range r {
            switch k {
            case "port", "code", "ca_name", "ca_sha", "host", "proto", "cidr", "local_cidr":
                if _, ok := v.(string); !ok {
                    return fmt.Errorf("%s rule #%d: field %q must be a quoted string, got %T", table, i, k, v)
                }
            case "groups":
                if _, ok := v.([]any); !ok && v != nil {
                    return fmt.Errorf("%s rule #%d: groups must be a list", table, i)
                }
            }
        }
    }
    return nil
}

Try / catch

if err := fw.AddFirewallRulesFromConfig(l, "inbound", rules); err != nil {
    // err looks like "inbound rule #2; ..." — surface it with config file context
    return fmt.Errorf("firewall config %s: %w", configFile, err)
}

Prevention

When it happens

Trigger: 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.

Common situations: YAML type coercion surprises (unquoted values that decode as ints/bools), typos in field structure, or programmatic config generation emitting wrong JSON/YAML types.

Related errors


AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03). Data as JSON: /api/errors/21100d3af7bf82b1. Report an issue: GitHub.