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

grpc / grpc-java / #18712

pending completion
#18712

push

github-actions

web-flow
Sort the policies in a rule by policy name when parsing from proto. (#10334)

* Sort the policies in a rule by policy name when parsing from proto.  This fixes the server sending a GOAWAY when an LDS update with no changes other than ordering is received.

* Remove use of deprecated method setSourceIp

* Fix style issues

* Update RbacFilterTest.java

30569 of 34649 relevant lines covered (88.22%)

0.88 hits per line

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

86.67
/../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.common.annotations.VisibleForTesting;
22
import com.google.protobuf.Any;
23
import com.google.protobuf.InvalidProtocolBufferException;
24
import com.google.protobuf.Message;
25
import io.envoyproxy.envoy.config.core.v3.CidrRange;
26
import io.envoyproxy.envoy.config.rbac.v3.Permission;
27
import io.envoyproxy.envoy.config.rbac.v3.Policy;
28
import io.envoyproxy.envoy.config.rbac.v3.Principal;
29
import io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC;
30
import io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBACPerRoute;
31
import io.envoyproxy.envoy.type.v3.Int32Range;
32
import io.grpc.Metadata;
33
import io.grpc.ServerCall;
34
import io.grpc.ServerCallHandler;
35
import io.grpc.ServerInterceptor;
36
import io.grpc.Status;
37
import io.grpc.xds.Filter.ServerInterceptorBuilder;
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, ServerInterceptorBuilder {
70
  private static final Logger logger = Logger.getLogger(RbacFilter.class.getName());
1✔
71

72
  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
  RbacFilter() {}
1✔
81

82
  @Override
83
  public String[] typeUrls() {
84
    return new String[] { TYPE_URL, TYPE_URL_OVERRIDE_CONFIG };
1✔
85
  }
86

87
  @Override
88
  public ConfigOrError<RbacConfig> parseFilterConfig(Message rawProtoMessage) {
89
    RBAC rbacProto;
90
    if (!(rawProtoMessage instanceof Any)) {
1✔
91
      return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());
×
92
    }
93
    Any anyMessage = (Any) rawProtoMessage;
1✔
94
    try {
95
      rbacProto = anyMessage.unpack(RBAC.class);
1✔
96
    } catch (InvalidProtocolBufferException e) {
×
97
      return ConfigOrError.fromError("Invalid proto: " + e);
×
98
    }
1✔
99
    return parseRbacConfig(rbacProto);
1✔
100
  }
101

102
  @VisibleForTesting
103
  static ConfigOrError<RbacConfig> parseRbacConfig(RBAC rbac) {
104
    if (!rbac.hasRules()) {
1✔
105
      return ConfigOrError.fromConfig(RbacConfig.create(null));
×
106
    }
107
    io.envoyproxy.envoy.config.rbac.v3.RBAC rbacConfig = rbac.getRules();
1✔
108
    GrpcAuthorizationEngine.Action authAction;
109
    switch (rbacConfig.getAction()) {
1✔
110
      case ALLOW:
111
        authAction = GrpcAuthorizationEngine.Action.ALLOW;
1✔
112
        break;
1✔
113
      case DENY:
114
        authAction = GrpcAuthorizationEngine.Action.DENY;
1✔
115
        break;
1✔
116
      case LOG:
117
        return ConfigOrError.fromConfig(RbacConfig.create(null));
1✔
118
      case UNRECOGNIZED:
119
      default:
120
        return ConfigOrError.fromError("Unknown rbacConfig action type: " + rbacConfig.getAction());
×
121
    }
122
    List<GrpcAuthorizationEngine.PolicyMatcher> policyMatchers = new ArrayList<>();
1✔
123
    List<Entry<String, Policy>> sortedPolicyEntries = rbacConfig.getPoliciesMap().entrySet()
1✔
124
        .stream()
1✔
125
        .sorted((a,b) -> a.getKey().compareTo(b.getKey()))
1✔
126
        .collect(Collectors.toList());
1✔
127
    for (Map.Entry<String, Policy> entry: sortedPolicyEntries) {
1✔
128
      try {
129
        Policy policy = entry.getValue();
1✔
130
        if (policy.hasCondition() || policy.hasCheckedCondition()) {
1✔
131
          return ConfigOrError.fromError(
1✔
132
                  "Policy.condition and Policy.checked_condition must not set: " + entry.getKey());
1✔
133
        }
134
        policyMatchers.add(PolicyMatcher.create(entry.getKey(),
1✔
135
                parsePermissionList(policy.getPermissionsList()),
1✔
136
                parsePrincipalList(policy.getPrincipalsList())));
1✔
137
      } catch (Exception e) {
1✔
138
        return ConfigOrError.fromError("Encountered error parsing policy: " + e);
1✔
139
      }
1✔
140
    }
1✔
141
    return ConfigOrError.fromConfig(RbacConfig.create(
1✔
142
        AuthConfig.create(policyMatchers, authAction)));
1✔
143
  }
144

145
  @Override
146
  public ConfigOrError<RbacConfig> parseFilterConfigOverride(Message rawProtoMessage) {
147
    RBACPerRoute rbacPerRoute;
148
    if (!(rawProtoMessage instanceof Any)) {
1✔
149
      return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());
×
150
    }
151
    Any anyMessage = (Any) rawProtoMessage;
1✔
152
    try {
153
      rbacPerRoute = anyMessage.unpack(RBACPerRoute.class);
1✔
154
    } catch (InvalidProtocolBufferException e) {
×
155
      return ConfigOrError.fromError("Invalid proto: " + e);
×
156
    }
1✔
157
    if (rbacPerRoute.hasRbac()) {
1✔
158
      return parseRbacConfig(rbacPerRoute.getRbac());
1✔
159
    } else {
160
      return ConfigOrError.fromConfig(RbacConfig.create(null));
1✔
161
    }
162
  }
163

164
  @Nullable
165
  @Override
166
  public ServerInterceptor buildServerInterceptor(FilterConfig config,
167
                                                  @Nullable FilterConfig overrideConfig) {
168
    checkNotNull(config, "config");
1✔
169
    if (overrideConfig != null) {
1✔
170
      config = overrideConfig;
1✔
171
    }
172
    AuthConfig authConfig = ((RbacConfig) config).authConfig();
1✔
173
    return authConfig == null ? null : generateAuthorizationInterceptor(authConfig);
1✔
174
  }
175

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

200
  private static OrMatcher parsePermissionList(List<Permission> permissions) {
201
    List<Matcher> anyMatch = new ArrayList<>();
1✔
202
    for (Permission permission : permissions) {
1✔
203
      anyMatch.add(parsePermission(permission));
1✔
204
    }
1✔
205
    return OrMatcher.create(anyMatch);
1✔
206
  }
207

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

243
  private static OrMatcher parsePrincipalList(List<Principal> principals) {
244
    List<Matcher> anyMatch = new ArrayList<>();
1✔
245
    for (Principal principal: principals) {
1✔
246
      anyMatch.add(parsePrincipal(principal));
1✔
247
    }
1✔
248
    return OrMatcher.create(anyMatch);
1✔
249
  }
250

251
  private static Matcher parsePrincipal(Principal principal) {
252
    switch (principal.getIdentifierCase()) {
1✔
253
      case OR_IDS:
254
        return parsePrincipalList(principal.getOrIds().getIdsList());
×
255
      case AND_IDS:
256
        List<Matcher> nextMatchers = new ArrayList<>();
1✔
257
        for (Principal next : principal.getAndIds().getIdsList()) {
1✔
258
          nextMatchers.add(parsePrincipal(next));
1✔
259
        }
1✔
260
        return AndMatcher.create(nextMatchers);
1✔
261
      case ANY:
262
        return AlwaysTrueMatcher.INSTANCE;
1✔
263
      case AUTHENTICATED:
264
        return parseAuthenticatedMatcher(principal.getAuthenticated());
1✔
265
      case DIRECT_REMOTE_IP:
266
        return createSourceIpMatcher(principal.getDirectRemoteIp());
1✔
267
      case REMOTE_IP:
268
        return createSourceIpMatcher(principal.getRemoteIp());
1✔
269
      case SOURCE_IP:
270
        return createSourceIpMatcher(principal.getSourceIp());
1✔
271
      case HEADER:
272
        return parseHeaderMatcher(principal.getHeader());
1✔
273
      case NOT_ID:
274
        return InvertMatcher.create(parsePrincipal(principal.getNotId()));
1✔
275
      case URL_PATH:
276
        return parsePathMatcher(principal.getUrlPath());
1✔
277
      case METADATA: // hard coded, never match.
278
        return InvertMatcher.create(AlwaysTrueMatcher.INSTANCE);
1✔
279
      case IDENTIFIER_NOT_SET:
280
      default:
281
        throw new IllegalArgumentException(
×
282
                "Unknown principal identifier case: " + principal.getIdentifierCase());
×
283
    }
284
  }
285

286
  private static PathMatcher parsePathMatcher(
287
          io.envoyproxy.envoy.type.matcher.v3.PathMatcher proto) {
288
    switch (proto.getRuleCase()) {
1✔
289
      case PATH:
290
        return PathMatcher.create(MatcherParser.parseStringMatcher(proto.getPath()));
1✔
291
      case RULE_NOT_SET:
292
      default:
293
        throw new IllegalArgumentException(
×
294
                "Unknown path matcher rule type: " + proto.getRuleCase());
×
295
    }
296
  }
297

298
  private static RequestedServerNameMatcher parseRequestedServerNameMatcher(
299
          io.envoyproxy.envoy.type.matcher.v3.StringMatcher proto) {
300
    return RequestedServerNameMatcher.create(MatcherParser.parseStringMatcher(proto));
1✔
301
  }
302

303
  private static AuthHeaderMatcher parseHeaderMatcher(
304
          io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto) {
305
    if (proto.getName().startsWith("grpc-")) {
1✔
306
      throw new IllegalArgumentException("Invalid header matcher config: [grpc-] prefixed "
1✔
307
          + "header name is not allowed.");
308
    }
309
    if (":scheme".equals(proto.getName())) {
1✔
310
      throw new IllegalArgumentException("Invalid header matcher config: header name [:scheme] "
×
311
          + "is not allowed.");
312
    }
313
    return AuthHeaderMatcher.create(MatcherParser.parseHeaderMatcher(proto));
1✔
314
  }
315

316
  private static AuthenticatedMatcher parseAuthenticatedMatcher(
317
          Principal.Authenticated proto) {
318
    Matchers.StringMatcher matcher = MatcherParser.parseStringMatcher(proto.getPrincipalName());
1✔
319
    return AuthenticatedMatcher.create(matcher);
1✔
320
  }
321

322
  private static DestinationPortMatcher createDestinationPortMatcher(int port) {
323
    return DestinationPortMatcher.create(port);
1✔
324
  }
325

326
  private static DestinationPortRangeMatcher parseDestinationPortRangeMatcher(Int32Range range) {
327
    return DestinationPortRangeMatcher.create(range.getStart(), range.getEnd());
1✔
328
  }
329

330
  private static DestinationIpMatcher createDestinationIpMatcher(CidrRange cidrRange) {
331
    return DestinationIpMatcher.create(Matchers.CidrMatcher.create(
1✔
332
            resolve(cidrRange), cidrRange.getPrefixLen().getValue()));
1✔
333
  }
334

335
  private static SourceIpMatcher createSourceIpMatcher(CidrRange cidrRange) {
336
    return SourceIpMatcher.create(Matchers.CidrMatcher.create(
1✔
337
            resolve(cidrRange), cidrRange.getPrefixLen().getValue()));
1✔
338
  }
339

340
  private static InetAddress resolve(CidrRange cidrRange) {
341
    try {
342
      return InetAddress.getByName(cidrRange.getAddressPrefix());
1✔
343
    } catch (UnknownHostException ex) {
×
344
      throw new IllegalArgumentException("IP address can not be found: " + ex);
×
345
    }
346
  }
347
}
348

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