slackhq/nebula · error

could not parse rule

Error message

could not parse rule

What it means

Not a sentinel: an anonymous error created inline in convertRule (firewall.go:950) with message "could not parse rule". convertRule converts a raw config value (from the firewall rules section of the YAML/JSON config) into a rule struct, and returns this error when the value is not a map[string]any — i.e. the rule entry in the config is structurally invalid before any per-field parsing happens. It aborts firewall rule loading so the operator knows the config is malformed.

Source

Thrown at firewall.go:950

type rule struct {
	Port      string
	Code      string
	Proto     string
	Host      string
	Groups    []string
	Cidr      string
	LocalCidr string
	CAName    string
	CASha     string
}

func convertRule(l *slog.Logger, p any, table string, i int) (rule, error) {
	r := rule{}

	m, ok := p.(map[string]any)
	if !ok {
		return r, errors.New("could not parse rule")
	}

	toString := func(k string, m map[string]any) string {
		v, ok := m[k]
		if !ok {
			return ""
		}
		return fmt.Sprintf("%v", v)
	}

	r.Port = toString("port", m)
	r.Code = toString("code", m)
	r.Proto = toString("proto", m)
	r.Host = toString("host", m)
	r.Cidr = toString("cidr", m)
	r.LocalCidr = toString("local_cidr", m)
	r.CAName = toString("ca_name", m)
	r.CASha = toString("ca_sha", m)

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Open the config at the reported table/index and make the rule entry an object/map with keys like port, proto, host, group, cidr
  2. Fix YAML indentation so each rule is a map element of the inbound/outbound list, not a scalar
  3. If rules are provided programmatically, pass map[string]any (decoded JSON object) per rule, not strings or arrays
  4. Run a config validation/dry-run before reload to catch malformed rule entries early

Example fix

// before (malformed YAML: rule collapsed to a string)
firewall:
  inbound:
    - "port: 443 proto: any"
// after
firewall:
  inbound:
    - port: 443
      proto: any
      host: web
Defensive patterns

Strategy: validation

Validate before calling

// validate every firewall rule entry is an object before handing config to the firewall
for i, r := range cfg.Firewall.Inbound {
    if _, ok := r.(map[string]any); !ok {
        return fmt.Errorf("firewall.inbound[%d] must be a mapping, got %T", i, r)
    }
}

Type guard

func isRuleMap(v any) bool {
    _, ok := v.(map[string]any)
    return ok
}

Try / catch

r, err := convertRule(logger, raw, "inbound", i)
if err != nil {
    if err.Error() == "could not parse rule" {
        // raw entry is not a map: log the index and offending value, then abort reload
    }
}

Prevention

When it happens

Trigger: Calling Firewall reload/add-firewall-rule config paths where an entry in the firewall.inbound/outbound list is not a mapping/object — e.g. it's a string, number, array, or null — so the type assertion p.(map[string]any) at firewall.go:950 fails.

Common situations: YAML indentation mistakes that turn a rule into a scalar or list; quoting a whole rule as a string; mixing list and map syntax; programmatically supplied rule JSON with wrong shape (array instead of object); config hot-reload picking up a partially written file.

Related errors


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