• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #20276

11 May 2026 08:24AM UTC coverage: 88.821% (-0.02%) from 88.838%
#20276

push

github

web-flow
Allow injecting bootstrap info into xDS Filter API for config parsing (#12724)

Extend the xDS Filter API to support injecting bootstrap information
into filters during configuration parsing. This allows filters to access
context information (e.g., allowed gRPC services) from the resource loading
layer during configuration validation and parsing.

- Update `Filter.Provider.parseFilterConfig` and
`parseFilterConfigOverride`
  to accept a `FilterContext` parameter.
- Introduce `BootstrapInfoGrpcServiceContextProvider` to encapsulate
  bootstrap info for context resolution.
- Update `XdsListenerResource` and `XdsRouteConfigureResource` to
  construct and pass `FilterContext` during configuration parsing.
- Update sub-filters (`FaultFilter`, `RbacFilter`,
`GcpAuthenticationFilter`,
  `RouterFilter`) to match the updated `FilterContext` signature.

Known Gaps & Limitations:
1. **MetricHolder**: Propagation of `MetricHolder` is not supported with
   this approach currently and is planned for support in a later phase.
2. **NameResolverRegistry**: Propagation is deferred for consistency.
While it could be passed from `XdsNameResolver` on the client side, there is
no equivalent mechanism on the server side. To ensure consistent
behavior, `DefaultRegistry` is used when validating schemes and creating channels.

36254 of 40817 relevant lines covered (88.82%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.33
/../xds/src/main/java/io/grpc/xds/RbacFilter.java
1
/*
2
 * Copyright 2021 The gRPC Authors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package io.grpc.xds;
18

19
import static com.google.common.base.Preconditions.checkNotNull;
20

21
import com.google.protobuf.Any;
22
import com.google.protobuf.InvalidProtocolBufferException;
23
import com.google.protobuf.Message;
24
import io.envoyproxy.envoy.config.core.v3.CidrRange;
25
import io.envoyproxy.envoy.config.rbac.v3.Permission;
26
import io.envoyproxy.envoy.config.rbac.v3.Policy;
27
import io.envoyproxy.envoy.config.rbac.v3.Principal;
28
import io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC;
29
import io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBACPerRoute;
30
import io.envoyproxy.envoy.type.v3.Int32Range;
31
import io.grpc.Metadata;
32
import io.grpc.ServerCall;
33
import io.grpc.ServerCallHandler;
34
import io.grpc.ServerInterceptor;
35
import io.grpc.Status;
36
import io.grpc.xds.internal.MatcherParser;
37
import io.grpc.xds.internal.Matchers;
38
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine;
39
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AlwaysTrueMatcher;
40
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AndMatcher;
41
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AuthConfig;
42
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AuthDecision;
43
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AuthHeaderMatcher;
44
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AuthenticatedMatcher;
45
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.DestinationIpMatcher;
46
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.DestinationPortMatcher;
47
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.DestinationPortRangeMatcher;
48
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.InvertMatcher;
49
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.Matcher;
50
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.OrMatcher;
51
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.PathMatcher;
52
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.PolicyMatcher;
53
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.RequestedServerNameMatcher;
54
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.SourceIpMatcher;
55
import java.net.InetAddress;
56
import java.net.UnknownHostException;
57
import java.util.ArrayList;
58
import java.util.List;
59
import java.util.Map;
60
import java.util.Map.Entry;
61
import java.util.logging.Level;
62
import java.util.logging.Logger;
63
import java.util.stream.Collectors;
64
import javax.annotation.Nullable;
65

66
/** RBAC Http filter implementation. */
67
final class RbacFilter implements Filter {
68
  private static final Logger logger = Logger.getLogger(RbacFilter.class.getName());
1✔
69

70
  private static final RbacFilter INSTANCE = new RbacFilter();
1✔
71

72
  static final String TYPE_URL =
73
          "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC";
74

75
  private static final String TYPE_URL_OVERRIDE_CONFIG =
76
          "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute";
77

78
  private RbacFilter() {}
79

80
  static final class Provider implements Filter.Provider {
1✔
81
    @Override
82
    public String[] typeUrls() {
83
      return new String[] {TYPE_URL, TYPE_URL_OVERRIDE_CONFIG};
1✔
84
    }
85

86
    @Override
87
    public boolean isServerFilter() {
88
      return true;
1✔
89
    }
90

91
    @Override
92
    public RbacFilter newInstance(String name) {
93
      return INSTANCE;
1✔
94
    }
95

96
    @Override
97
    public ConfigOrError<RbacConfig> parseFilterConfig(
98
        Message rawProtoMessage, FilterConfigParseContext context) {
99
      RBAC rbacProto;
100
      if (!(rawProtoMessage instanceof Any)) {
1✔
101
        return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());
×
102
      }
103
      Any anyMessage = (Any) rawProtoMessage;
1✔
104
      try {
105
        rbacProto = anyMessage.unpack(RBAC.class);
1✔
106
      } catch (InvalidProtocolBufferException e) {
×
107
        return ConfigOrError.fromError("Invalid proto: " + e);
×
108
      }
1✔
109
      return parseRbacConfig(rbacProto);
1✔
110
    }
111

112
    @Override
113
    public ConfigOrError<RbacConfig> parseFilterConfigOverride(
114
        Message rawProtoMessage, FilterConfigParseContext context) {
115
      RBACPerRoute rbacPerRoute;
116
      if (!(rawProtoMessage instanceof Any)) {
1✔
117
        return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());
×
118
      }
119
      Any anyMessage = (Any) rawProtoMessage;
1✔
120
      try {
121
        rbacPerRoute = anyMessage.unpack(RBACPerRoute.class);
1✔
122
      } catch (InvalidProtocolBufferException e) {
×
123
        return ConfigOrError.fromError("Invalid proto: " + e);
×
124
      }
1✔
125
      if (rbacPerRoute.hasRbac()) {
1✔
126
        return parseRbacConfig(rbacPerRoute.getRbac());
1✔
127
      } else {
128
        return ConfigOrError.fromConfig(RbacConfig.create(null));
1✔
129
      }
130
    }
131

132
    static ConfigOrError<RbacConfig> parseRbacConfig(RBAC rbac) {
133
      if (!rbac.hasRules()) {
1✔
134
        return ConfigOrError.fromConfig(RbacConfig.create(null));
×
135
      }
136
      io.envoyproxy.envoy.config.rbac.v3.RBAC rbacConfig = rbac.getRules();
1✔
137
      GrpcAuthorizationEngine.Action authAction;
138
      switch (rbacConfig.getAction()) {
1✔
139
        case ALLOW:
140
          authAction = GrpcAuthorizationEngine.Action.ALLOW;
1✔
141
          break;
1✔
142
        case DENY:
143
          authAction = GrpcAuthorizationEngine.Action.DENY;
1✔
144
          break;
1✔
145
        case LOG:
146
          return ConfigOrError.fromConfig(RbacConfig.create(null));
1✔
147
        case UNRECOGNIZED:
148
        default:
149
          return ConfigOrError.fromError(
×
150
              "Unknown rbacConfig action type: " + rbacConfig.getAction());
×
151
      }
152
      List<GrpcAuthorizationEngine.PolicyMatcher> policyMatchers = new ArrayList<>();
1✔
153
      List<Entry<String, Policy>> sortedPolicyEntries = rbacConfig.getPoliciesMap().entrySet()
1✔
154
          .stream()
1✔
155
          .sorted((a,b) -> a.getKey().compareTo(b.getKey()))
1✔
156
          .collect(Collectors.toList());
1✔
157
      for (Map.Entry<String, Policy> entry: sortedPolicyEntries) {
1✔
158
        try {
159
          Policy policy = entry.getValue();
1✔
160
          if (policy.hasCondition() || policy.hasCheckedCondition()) {
1✔
161
            return ConfigOrError.fromError(
1✔
162
                "Policy.condition and Policy.checked_condition must not set: " + entry.getKey());
1✔
163
          }
164
          policyMatchers.add(PolicyMatcher.create(entry.getKey(),
1✔
165
              parsePermissionList(policy.getPermissionsList()),
1✔
166
              parsePrincipalList(policy.getPrincipalsList())));
1✔
167
        } catch (Exception e) {
1✔
168
          return ConfigOrError.fromError("Encountered error parsing policy: " + e);
1✔
169
        }
1✔
170
      }
1✔
171
      return ConfigOrError.fromConfig(RbacConfig.create(
1✔
172
          AuthConfig.create(policyMatchers, authAction)));
1✔
173
    }
174
  }
175

176
  @Nullable
177
  @Override
178
  public ServerInterceptor buildServerInterceptor(FilterConfig config,
179
                                                  @Nullable FilterConfig overrideConfig) {
180
    checkNotNull(config, "config");
1✔
181
    if (overrideConfig != null) {
1✔
182
      config = overrideConfig;
1✔
183
    }
184
    AuthConfig authConfig = ((RbacConfig) config).authConfig();
1✔
185
    return authConfig == null ? null : generateAuthorizationInterceptor(authConfig);
1✔
186
  }
187

188
  private ServerInterceptor generateAuthorizationInterceptor(AuthConfig config) {
189
    checkNotNull(config, "config");
1✔
190
    final GrpcAuthorizationEngine authEngine = new GrpcAuthorizationEngine(config);
1✔
191
    return new ServerInterceptor() {
1✔
192
        @Override
193
        public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
194
                final ServerCall<ReqT, RespT> call,
195
                final Metadata headers, ServerCallHandler<ReqT, RespT> next) {
196
          AuthDecision authResult = authEngine.evaluate(headers, call);
1✔
197
          if (logger.isLoggable(Level.FINE)) {
1✔
198
            logger.log(Level.FINE,
×
199
                "Authorization result for serverCall {0}: {1}, matching policy: {2}.",
200
                new Object[]{call, authResult.decision(), authResult.matchingPolicyName()});
×
201
          }
202
          if (GrpcAuthorizationEngine.Action.DENY.equals(authResult.decision())) {
1✔
203
            Status status = Status.PERMISSION_DENIED.withDescription("Access Denied");
1✔
204
            call.close(status, new Metadata());
1✔
205
            return new ServerCall.Listener<ReqT>(){};
1✔
206
          }
207
          return next.startCall(call, headers);
1✔
208
        }
209
    };
210
  }
211

212
  private static OrMatcher parsePermissionList(List<Permission> permissions) {
213
    List<Matcher> anyMatch = new ArrayList<>();
1✔
214
    for (Permission permission : permissions) {
1✔
215
      anyMatch.add(parsePermission(permission));
1✔
216
    }
1✔
217
    return OrMatcher.create(anyMatch);
1✔
218
  }
219

220
  private static Matcher parsePermission(Permission permission) {
221
    switch (permission.getRuleCase()) {
1✔
222
      case AND_RULES:
223
        List<Matcher> andMatch = new ArrayList<>();
1✔
224
        for (Permission p : permission.getAndRules().getRulesList()) {
1✔
225
          andMatch.add(parsePermission(p));
1✔
226
        }
1✔
227
        return AndMatcher.create(andMatch);
1✔
228
      case OR_RULES:
229
        return parsePermissionList(permission.getOrRules().getRulesList());
1✔
230
      case ANY:
231
        return AlwaysTrueMatcher.INSTANCE;
1✔
232
      case HEADER:
233
        return parseHeaderMatcher(permission.getHeader());
1✔
234
      case URL_PATH:
235
        return parsePathMatcher(permission.getUrlPath());
1✔
236
      case DESTINATION_IP:
237
        return createDestinationIpMatcher(permission.getDestinationIp());
1✔
238
      case DESTINATION_PORT:
239
        return createDestinationPortMatcher(permission.getDestinationPort());
1✔
240
      case DESTINATION_PORT_RANGE:
241
        return parseDestinationPortRangeMatcher(permission.getDestinationPortRange());
1✔
242
      case NOT_RULE:
243
        return InvertMatcher.create(parsePermission(permission.getNotRule()));
1✔
244
      case METADATA: // hard coded, never match.
245
        return InvertMatcher.create(AlwaysTrueMatcher.INSTANCE);
1✔
246
      case REQUESTED_SERVER_NAME:
247
        return parseRequestedServerNameMatcher(permission.getRequestedServerName());
1✔
248
      case RULE_NOT_SET:
249
      default:
250
        throw new IllegalArgumentException(
1✔
251
                "Unknown permission rule case: " + permission.getRuleCase());
1✔
252
    }
253
  }
254

255
  private static OrMatcher parsePrincipalList(List<Principal> principals) {
256
    List<Matcher> anyMatch = new ArrayList<>();
1✔
257
    for (Principal principal: principals) {
1✔
258
      anyMatch.add(parsePrincipal(principal));
1✔
259
    }
1✔
260
    return OrMatcher.create(anyMatch);
1✔
261
  }
262

263
  private static Matcher parsePrincipal(Principal principal) {
264
    switch (principal.getIdentifierCase()) {
1✔
265
      case OR_IDS:
266
        return parsePrincipalList(principal.getOrIds().getIdsList());
×
267
      case AND_IDS:
268
        List<Matcher> nextMatchers = new ArrayList<>();
1✔
269
        for (Principal next : principal.getAndIds().getIdsList()) {
1✔
270
          nextMatchers.add(parsePrincipal(next));
1✔
271
        }
1✔
272
        return AndMatcher.create(nextMatchers);
1✔
273
      case ANY:
274
        return AlwaysTrueMatcher.INSTANCE;
1✔
275
      case AUTHENTICATED:
276
        return parseAuthenticatedMatcher(principal.getAuthenticated());
1✔
277
      case DIRECT_REMOTE_IP:
278
        return createSourceIpMatcher(principal.getDirectRemoteIp());
1✔
279
      case REMOTE_IP:
280
        return createSourceIpMatcher(principal.getRemoteIp());
1✔
281
      case SOURCE_IP: {
282
        // gRFC A41 has identical handling of source_ip as remote_ip and direct_remote_ip and
283
        // pre-dates the deprecation.
284
        @SuppressWarnings("deprecation")
285
        CidrRange sourceIp = principal.getSourceIp();
1✔
286
        return createSourceIpMatcher(sourceIp);
1✔
287
      }
288
      case HEADER:
289
        return parseHeaderMatcher(principal.getHeader());
1✔
290
      case NOT_ID:
291
        return InvertMatcher.create(parsePrincipal(principal.getNotId()));
1✔
292
      case URL_PATH:
293
        return parsePathMatcher(principal.getUrlPath());
1✔
294
      case METADATA: // hard coded, never match.
295
        return InvertMatcher.create(AlwaysTrueMatcher.INSTANCE);
1✔
296
      case IDENTIFIER_NOT_SET:
297
      default:
298
        throw new IllegalArgumentException(
×
299
                "Unknown principal identifier case: " + principal.getIdentifierCase());
×
300
    }
301
  }
302

303
  private static PathMatcher parsePathMatcher(
304
          io.envoyproxy.envoy.type.matcher.v3.PathMatcher proto) {
305
    switch (proto.getRuleCase()) {
1✔
306
      case PATH:
307
        return PathMatcher.create(MatcherParser.parseStringMatcher(proto.getPath()));
1✔
308
      case RULE_NOT_SET:
309
      default:
310
        throw new IllegalArgumentException(
×
311
                "Unknown path matcher rule type: " + proto.getRuleCase());
×
312
    }
313
  }
314

315
  private static RequestedServerNameMatcher parseRequestedServerNameMatcher(
316
          io.envoyproxy.envoy.type.matcher.v3.StringMatcher proto) {
317
    return RequestedServerNameMatcher.create(MatcherParser.parseStringMatcher(proto));
1✔
318
  }
319

320
  private static AuthHeaderMatcher parseHeaderMatcher(
321
          io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto) {
322
    if (proto.getName().startsWith("grpc-")) {
1✔
323
      throw new IllegalArgumentException("Invalid header matcher config: [grpc-] prefixed "
1✔
324
          + "header name is not allowed.");
325
    }
326
    if (":scheme".equals(proto.getName())) {
1✔
327
      throw new IllegalArgumentException("Invalid header matcher config: header name [:scheme] "
×
328
          + "is not allowed.");
329
    }
330
    return AuthHeaderMatcher.create(MatcherParser.parseHeaderMatcher(proto));
1✔
331
  }
332

333
  private static AuthenticatedMatcher parseAuthenticatedMatcher(
334
          Principal.Authenticated proto) {
335
    Matchers.StringMatcher matcher = MatcherParser.parseStringMatcher(proto.getPrincipalName());
1✔
336
    return AuthenticatedMatcher.create(matcher);
1✔
337
  }
338

339
  private static DestinationPortMatcher createDestinationPortMatcher(int port) {
340
    return DestinationPortMatcher.create(port);
1✔
341
  }
342

343
  private static DestinationPortRangeMatcher parseDestinationPortRangeMatcher(Int32Range range) {
344
    return DestinationPortRangeMatcher.create(range.getStart(), range.getEnd());
1✔
345
  }
346

347
  private static DestinationIpMatcher createDestinationIpMatcher(CidrRange cidrRange) {
348
    return DestinationIpMatcher.create(Matchers.CidrMatcher.create(
1✔
349
            resolve(cidrRange), cidrRange.getPrefixLen().getValue()));
1✔
350
  }
351

352
  private static SourceIpMatcher createSourceIpMatcher(CidrRange cidrRange) {
353
    return SourceIpMatcher.create(Matchers.CidrMatcher.create(
1✔
354
            resolve(cidrRange), cidrRange.getPrefixLen().getValue()));
1✔
355
  }
356

357
  private static InetAddress resolve(CidrRange cidrRange) {
358
    try {
359
      return InetAddress.getByName(cidrRange.getAddressPrefix());
1✔
360
    } catch (UnknownHostException ex) {
×
361
      throw new IllegalArgumentException("IP address can not be found: " + ex);
×
362
    }
363
  }
364
}
365

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc