grpc/grpc-go · error

rbac: error constructing matching engine

Error message

rbac: error constructing matching engine: %v

What it means

parseConfig (rbac.go:112) wraps an error from rbac.NewChainEngine, which builds the policy-matching engine from the RBAC rules. Construction can fail on malformed permissions/principals, unsupported matcher types, or invalid regex/CEL-like expressions even after A41 header-name validation passes.

Solutions

  1. Read the wrapped error from rbac.NewChainEngine to find the offending matcher or permission.
  2. Simplify the policy (remove the offending permission/principal) to isolate the failing rule.
  3. Align grpc-go and go-control-plane versions so the matcher library understands the policy shape.

Example fix

// before: policy references an unsupported matcher
permissions: [{ rule: { destination_port: { range: { start: 0, end: 0 } } } }]

// after: use a supported permission kind
permissions: [{ rule: { url_path: { path: { exact: "/foo" } } } }]
Defensive patterns

Strategy: validation

Validate before calling

// Validate the policy compiles standalone before shipping it:
ce, err := rbac.NewChainEngine([]*v3rbacpb.RBAC{policy}, "")
if err != nil {
    return fmt.Errorf("policy does not compile: %w", err)
}

Try / catch

fc, err := rbacBuilder.ParseFilterConfig(anyCfg)
if err != nil && strings.Contains(err.Error(), "constructing matching engine") {
    // strip permissions/principals one by one to isolate the failing rule
}

Prevention

When it happens

Trigger: An RBAC policy passes the A41 condition/header-name checks but contains a permission or principal that the chain engine cannot compile — e.g., an unknown permission/principal identifier kind, an invalid regex, or a malformed matcher.

Common situations: Control-plane emits a policy that uses a matcher grpc-go does not yet support (version skew); hand-authored RBAC config with a typo'd regex; partial upgrade of the rbac matcher library.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/6f67689f50896f17. Report an issue: GitHub.

Appendix: source

Thrown at internal/xds/httpfilter/rbac/rbac.go:112

	// Two cases where this HTTP Filter is a no op:
	// "If absent, no enforcing RBAC policy will be applied" - RBAC
	// Documentation for Rules field.
	// "At this time, if the RBAC.action is Action.LOG then the policy will be
	// completely ignored, as if RBAC was not configured." - A41
	if rbacCfg.Rules == nil || rbacCfg.GetRules().GetAction() == v3rbacpb.RBAC_LOG {
		return config{}, nil
	}

	// TODO(gregorycooke) - change the call chain to here so we have the filter
	// name to input here instead of an empty string. It will come from here:
	// https://github.com/grpc/grpc-go/blob/eff0942e95d93112921414aee758e619ec86f26f/xds/internal/xdsclient/xdsresource/unmarshal_lds.go#L199
	ce, err := rbac.NewChainEngine([]*v3rbacpb.RBAC{rbacCfg.GetRules()}, "")
	if err != nil {
		// "At this time, if the RBAC.action is Action.LOG then the policy will be
		// completely ignored, as if RBAC was not configured." - A41
		if rbacCfg.GetRules().GetAction() != v3rbacpb.RBAC_LOG {
			return nil, fmt.Errorf("rbac: error constructing matching engine: %v", err)
		}
	}

	return config{chainEngine: ce}, nil
}

// normalizePermissionHeaders applies the A41 header-name rules to every header
// matcher reachable from permission, including those nested inside and/or/not
// rules.
func normalizePermissionHeaders(permission *v3rbacpb.Permission) error {
	switch p := permission.GetRule().(type) {
	case *v3rbacpb.Permission_Header:
		return normalizeHeaderMatcher(p.Header)
	case *v3rbacpb.Permission_AndRules:
		for _, rule := range p.AndRules.GetRules() {
			if err := normalizePermissionHeaders(rule); err != nil {
				return err
			}

View on GitHub (pinned to 0c51461d27)