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

grpc / grpc-java / #19778

11 Apr 2025 03:25PM UTC coverage: 88.572% (-0.01%) from 88.586%
#19778

push

github

web-flow
xds: Enable deprecation warnings

The security code referenced fields removed from gRFC A29 before it was
finalized.

Note that this fixes a bug in CommonTlsContextUtil where
CombinedValidationContext was not checked. I believe this was the only
location with such a bug as I audited all non-test usages of
has/getValidationContext() and confirmed they have have a corresponding
has/getCombinedValidationContext().

34707 of 39185 relevant lines covered (88.57%)

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(Message rawProtoMessage) {
98
      RBAC rbacProto;
99
      if (!(rawProtoMessage instanceof Any)) {
1✔
100
        return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());
×
101
      }
102
      Any anyMessage = (Any) rawProtoMessage;
1✔
103
      try {
104
        rbacProto = anyMessage.unpack(RBAC.class);
1✔
105
      } catch (InvalidProtocolBufferException e) {
×
106
        return ConfigOrError.fromError("Invalid proto: " + e);
×
107
      }
1✔
108
      return parseRbacConfig(rbacProto);
1✔
109
    }
110

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

© 2025 Coveralls, Inc