grpc/grpc-go · error

rbac: principal header matcher for %v is :scheme or starts w

Error message

rbac: principal header matcher for %v is :scheme or starts with grpc

What it means

Per gRFC A41, an RBAC policy is invalid if any Principal has a header matcher whose name is ':scheme' or starts with 'grpc-' (rbac.go:74). grpc-go controls these headers internally, so matching on them is unsupported and the policy is rejected at config time.

Source

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

// Parsing is the same for the base config and the override config.
func parseConfig(rbacCfg *rpb.RBAC) (httpfilter.FilterConfig, error) {
	// All the validation logic described in A41.
	for _, policy := range rbacCfg.GetRules().GetPolicies() {
		// "Policy.condition and Policy.checked_condition must cause a
		// validation failure if present." - A41
		if policy.Condition != nil {
			return nil, errors.New("rbac: Policy.condition is present")
		}
		if policy.CheckedCondition != nil {
			return nil, errors.New("rbac: policy.CheckedCondition is present")
		}

		// "It is also a validation failure if Permission or Principal has a
		// header matcher for a grpc- prefixed header name or :scheme." - A41
		for _, principal := range policy.Principals {
			name := principal.GetHeader().GetName()
			if name == ":scheme" || strings.HasPrefix(name, "grpc-") {
				return nil, fmt.Errorf("rbac: principal header matcher for %v is :scheme or starts with grpc", name)
			}
		}
		for _, permission := range policy.Permissions {
			name := permission.GetHeader().GetName()
			if name == ":scheme" || strings.HasPrefix(name, "grpc-") {
				return nil, fmt.Errorf("rbac: permission header matcher for %v is :scheme or starts with grpc", name)
			}
		}
	}

	// "Envoy aliases :authority and Host in its header map implementation, so
	// they should be treated equivalent for the RBAC matchers; there must be no
	// behavior change depending on which of the two header names is used in the
	// RBAC policy." - A41. Loop through config's principals and policies, change
	// any header matcher with value "host" to :authority", as that is what
	// grpc-go shifts both headers to in transport layer.
	for _, policy := range rbacCfg.GetRules().GetPolicies() {
		for _, principal := range policy.Principals {

View on GitHub (pinned to 03255a9237)

Solutions

  1. Remove header matchers on ':scheme' and any 'grpc-'-prefixed header from RBAC principals (and permissions, which are checked similarly).
  2. If you need to gate on gRPC metadata, match on the corresponding well-known metadata via supported mechanisms rather than raw header names.
  3. Validate generated RBAC policies against the A41 restrictions before publishing.

Example fix

// before
policy.Principals = append(policy.Principals, &rbacpb.Principal{Identifier: &rbacpb.Principal_Header{Header: &routepb.HeaderMatcher{Name: ":scheme"}}})

// after: drop the unsupported header matcher (or match a supported header)
policy.Principals = append(policy.Principals, &rbacpb.Principal{Identifier: &rbacpb.Principal_Header{Header: &routepb.HeaderMatcher{Name: ":authority"}}})
Defensive patterns

Strategy: validation

Validate before calling

for _, p := range rbacCfg.GetRules().GetPolicies() {
    for _, pr := range p.GetPrincipals() {
        n := pr.GetHeader().GetName()
        if n == ":scheme" || strings.HasPrefix(n, "grpc-") {
            return fmt.Errorf("unsupported header matcher: %s", n)
        }
    }
}

Try / catch

fc, err := builder{}.ParseFilterConfig(anyMsg)
if err != nil {
    return err
}

Prevention

When it happens

Trigger: An RBAC rules policy contains a Principal.Identifier.header with name ':scheme' or a name beginning with 'grpc-'.

Common situations: RBAC config ported from Envoy/HTTP semantics that matched scheme or grpc metadata headers; generic header-based authz policy; template that lists all headers as matchers.

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/d76328f9ff4650c2. Report an issue: GitHub.