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

grpc / grpc-java / #20337

26 Jun 2026 11:37AM UTC coverage: 89.06% (+0.2%) from 88.876%
#20337

push

github

web-flow
Ext_proc filter and client interceptor (#12792)

Implements ext_proc filter from [grfc A93](https://github.com/grpc/proposal/pull/484)  (internal [design doc](http://go/ext-proc-design-java)).

37585 of 42202 relevant lines covered (89.06%)

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.Filter.FilterConfigParseContext;
37
import io.grpc.xds.Filter.FilterContext;
38
import io.grpc.xds.internal.MatcherParser;
39
import io.grpc.xds.internal.Matchers;
40
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine;
41
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AlwaysTrueMatcher;
42
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AndMatcher;
43
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AuthConfig;
44
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AuthDecision;
45
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AuthHeaderMatcher;
46
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AuthenticatedMatcher;
47
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.DestinationIpMatcher;
48
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.DestinationPortMatcher;
49
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.DestinationPortRangeMatcher;
50
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.InvertMatcher;
51
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.Matcher;
52
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.OrMatcher;
53
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.PathMatcher;
54
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.PolicyMatcher;
55
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.RequestedServerNameMatcher;
56
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.SourceIpMatcher;
57
import java.net.InetAddress;
58
import java.net.UnknownHostException;
59
import java.util.ArrayList;
60
import java.util.List;
61
import java.util.Map;
62
import java.util.Map.Entry;
63
import java.util.logging.Level;
64
import java.util.logging.Logger;
65
import java.util.stream.Collectors;
66
import javax.annotation.Nullable;
67

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

72
  private static final RbacFilter INSTANCE = new RbacFilter();
1✔
73

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

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

80
  private RbacFilter() {}
81

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

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

93
    @Override
94
    public RbacFilter newInstance(FilterContext context) {
95
      return INSTANCE;
1✔
96
    }
97

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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