• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.logging.Level;
63 import java.util.logging.Logger;
64 import javax.annotation.Nullable;
65 
66 /** RBAC Http filter implementation. */
67 final class RbacFilter implements Filter, ServerInterceptorBuilder {
68   private static final Logger logger = Logger.getLogger(RbacFilter.class.getName());
69 
70   static final RbacFilter INSTANCE = new RbacFilter();
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 
RbacFilter()78   RbacFilter() {}
79 
80   @Override
typeUrls()81   public String[] typeUrls() {
82     return new String[] { TYPE_URL, TYPE_URL_OVERRIDE_CONFIG };
83   }
84 
85   @Override
parseFilterConfig(Message rawProtoMessage)86   public ConfigOrError<RbacConfig> parseFilterConfig(Message rawProtoMessage) {
87     RBAC rbacProto;
88     if (!(rawProtoMessage instanceof Any)) {
89       return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());
90     }
91     Any anyMessage = (Any) rawProtoMessage;
92     try {
93       rbacProto = anyMessage.unpack(RBAC.class);
94     } catch (InvalidProtocolBufferException e) {
95       return ConfigOrError.fromError("Invalid proto: " + e);
96     }
97     return parseRbacConfig(rbacProto);
98   }
99 
100   @VisibleForTesting
parseRbacConfig(RBAC rbac)101   static ConfigOrError<RbacConfig> parseRbacConfig(RBAC rbac) {
102     if (!rbac.hasRules()) {
103       return ConfigOrError.fromConfig(RbacConfig.create(null));
104     }
105     io.envoyproxy.envoy.config.rbac.v3.RBAC rbacConfig = rbac.getRules();
106     GrpcAuthorizationEngine.Action authAction;
107     switch (rbacConfig.getAction()) {
108       case ALLOW:
109         authAction = GrpcAuthorizationEngine.Action.ALLOW;
110         break;
111       case DENY:
112         authAction = GrpcAuthorizationEngine.Action.DENY;
113         break;
114       case LOG:
115         return ConfigOrError.fromConfig(RbacConfig.create(null));
116       case UNRECOGNIZED:
117       default:
118         return ConfigOrError.fromError("Unknown rbacConfig action type: " + rbacConfig.getAction());
119     }
120     Map<String, Policy> policyMap = rbacConfig.getPoliciesMap();
121     List<GrpcAuthorizationEngine.PolicyMatcher> policyMatchers = new ArrayList<>();
122     for (Map.Entry<String, Policy> entry: policyMap.entrySet()) {
123       try {
124         Policy policy = entry.getValue();
125         if (policy.hasCondition() || policy.hasCheckedCondition()) {
126           return ConfigOrError.fromError(
127                   "Policy.condition and Policy.checked_condition must not set: " + entry.getKey());
128         }
129         policyMatchers.add(PolicyMatcher.create(entry.getKey(),
130                 parsePermissionList(policy.getPermissionsList()),
131                 parsePrincipalList(policy.getPrincipalsList())));
132       } catch (Exception e) {
133         return ConfigOrError.fromError("Encountered error parsing policy: " + e);
134       }
135     }
136     return ConfigOrError.fromConfig(RbacConfig.create(
137         AuthConfig.create(policyMatchers, authAction)));
138   }
139 
140   @Override
parseFilterConfigOverride(Message rawProtoMessage)141   public ConfigOrError<RbacConfig> parseFilterConfigOverride(Message rawProtoMessage) {
142     RBACPerRoute rbacPerRoute;
143     if (!(rawProtoMessage instanceof Any)) {
144       return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());
145     }
146     Any anyMessage = (Any) rawProtoMessage;
147     try {
148       rbacPerRoute = anyMessage.unpack(RBACPerRoute.class);
149     } catch (InvalidProtocolBufferException e) {
150       return ConfigOrError.fromError("Invalid proto: " + e);
151     }
152     if (rbacPerRoute.hasRbac()) {
153       return parseRbacConfig(rbacPerRoute.getRbac());
154     } else {
155       return ConfigOrError.fromConfig(RbacConfig.create(null));
156     }
157   }
158 
159   @Nullable
160   @Override
buildServerInterceptor(FilterConfig config, @Nullable FilterConfig overrideConfig)161   public ServerInterceptor buildServerInterceptor(FilterConfig config,
162                                                   @Nullable FilterConfig overrideConfig) {
163     checkNotNull(config, "config");
164     if (overrideConfig != null) {
165       config = overrideConfig;
166     }
167     AuthConfig authConfig = ((RbacConfig) config).authConfig();
168     return authConfig == null ? null : generateAuthorizationInterceptor(authConfig);
169   }
170 
generateAuthorizationInterceptor(AuthConfig config)171   private ServerInterceptor generateAuthorizationInterceptor(AuthConfig config) {
172     checkNotNull(config, "config");
173     final GrpcAuthorizationEngine authEngine = new GrpcAuthorizationEngine(config);
174     return new ServerInterceptor() {
175         @Override
176         public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
177                 final ServerCall<ReqT, RespT> call,
178                 final Metadata headers, ServerCallHandler<ReqT, RespT> next) {
179           AuthDecision authResult = authEngine.evaluate(headers, call);
180           if (logger.isLoggable(Level.FINE)) {
181             logger.log(Level.FINE,
182                 "Authorization result for serverCall {0}: {1}, matching policy: {2}.",
183                 new Object[]{call, authResult.decision(), authResult.matchingPolicyName()});
184           }
185           if (GrpcAuthorizationEngine.Action.DENY.equals(authResult.decision())) {
186             Status status = Status.PERMISSION_DENIED.withDescription("Access Denied");
187             call.close(status, new Metadata());
188             return new ServerCall.Listener<ReqT>(){};
189           }
190           return next.startCall(call, headers);
191         }
192     };
193   }
194 
195   private static OrMatcher parsePermissionList(List<Permission> permissions) {
196     List<Matcher> anyMatch = new ArrayList<>();
197     for (Permission permission : permissions) {
198       anyMatch.add(parsePermission(permission));
199     }
200     return OrMatcher.create(anyMatch);
201   }
202 
203   private static Matcher parsePermission(Permission permission) {
204     switch (permission.getRuleCase()) {
205       case AND_RULES:
206         List<Matcher> andMatch = new ArrayList<>();
207         for (Permission p : permission.getAndRules().getRulesList()) {
208           andMatch.add(parsePermission(p));
209         }
210         return AndMatcher.create(andMatch);
211       case OR_RULES:
212         return parsePermissionList(permission.getOrRules().getRulesList());
213       case ANY:
214         return AlwaysTrueMatcher.INSTANCE;
215       case HEADER:
216         return parseHeaderMatcher(permission.getHeader());
217       case URL_PATH:
218         return parsePathMatcher(permission.getUrlPath());
219       case DESTINATION_IP:
220         return createDestinationIpMatcher(permission.getDestinationIp());
221       case DESTINATION_PORT:
222         return createDestinationPortMatcher(permission.getDestinationPort());
223       case DESTINATION_PORT_RANGE:
224         return parseDestinationPortRangeMatcher(permission.getDestinationPortRange());
225       case NOT_RULE:
226         return InvertMatcher.create(parsePermission(permission.getNotRule()));
227       case METADATA: // hard coded, never match.
228         return InvertMatcher.create(AlwaysTrueMatcher.INSTANCE);
229       case REQUESTED_SERVER_NAME:
230         return parseRequestedServerNameMatcher(permission.getRequestedServerName());
231       case RULE_NOT_SET:
232       default:
233         throw new IllegalArgumentException(
234                 "Unknown permission rule case: " + permission.getRuleCase());
235     }
236   }
237 
238   private static OrMatcher parsePrincipalList(List<Principal> principals) {
239     List<Matcher> anyMatch = new ArrayList<>();
240     for (Principal principal: principals) {
241       anyMatch.add(parsePrincipal(principal));
242     }
243     return OrMatcher.create(anyMatch);
244   }
245 
246   private static Matcher parsePrincipal(Principal principal) {
247     switch (principal.getIdentifierCase()) {
248       case OR_IDS:
249         return parsePrincipalList(principal.getOrIds().getIdsList());
250       case AND_IDS:
251         List<Matcher> nextMatchers = new ArrayList<>();
252         for (Principal next : principal.getAndIds().getIdsList()) {
253           nextMatchers.add(parsePrincipal(next));
254         }
255         return AndMatcher.create(nextMatchers);
256       case ANY:
257         return AlwaysTrueMatcher.INSTANCE;
258       case AUTHENTICATED:
259         return parseAuthenticatedMatcher(principal.getAuthenticated());
260       case DIRECT_REMOTE_IP:
261         return createSourceIpMatcher(principal.getDirectRemoteIp());
262       case REMOTE_IP:
263         return createSourceIpMatcher(principal.getRemoteIp());
264       case SOURCE_IP:
265         return createSourceIpMatcher(principal.getSourceIp());
266       case HEADER:
267         return parseHeaderMatcher(principal.getHeader());
268       case NOT_ID:
269         return InvertMatcher.create(parsePrincipal(principal.getNotId()));
270       case URL_PATH:
271         return parsePathMatcher(principal.getUrlPath());
272       case METADATA: // hard coded, never match.
273         return InvertMatcher.create(AlwaysTrueMatcher.INSTANCE);
274       case IDENTIFIER_NOT_SET:
275       default:
276         throw new IllegalArgumentException(
277                 "Unknown principal identifier case: " + principal.getIdentifierCase());
278     }
279   }
280 
281   private static PathMatcher parsePathMatcher(
282           io.envoyproxy.envoy.type.matcher.v3.PathMatcher proto) {
283     switch (proto.getRuleCase()) {
284       case PATH:
285         return PathMatcher.create(MatcherParser.parseStringMatcher(proto.getPath()));
286       case RULE_NOT_SET:
287       default:
288         throw new IllegalArgumentException(
289                 "Unknown path matcher rule type: " + proto.getRuleCase());
290     }
291   }
292 
293   private static RequestedServerNameMatcher parseRequestedServerNameMatcher(
294           io.envoyproxy.envoy.type.matcher.v3.StringMatcher proto) {
295     return RequestedServerNameMatcher.create(MatcherParser.parseStringMatcher(proto));
296   }
297 
298   private static AuthHeaderMatcher parseHeaderMatcher(
299           io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto) {
300     if (proto.getName().startsWith("grpc-")) {
301       throw new IllegalArgumentException("Invalid header matcher config: [grpc-] prefixed "
302           + "header name is not allowed.");
303     }
304     if (":scheme".equals(proto.getName())) {
305       throw new IllegalArgumentException("Invalid header matcher config: header name [:scheme] "
306           + "is not allowed.");
307     }
308     return AuthHeaderMatcher.create(MatcherParser.parseHeaderMatcher(proto));
309   }
310 
311   private static AuthenticatedMatcher parseAuthenticatedMatcher(
312           Principal.Authenticated proto) {
313     Matchers.StringMatcher matcher = MatcherParser.parseStringMatcher(proto.getPrincipalName());
314     return AuthenticatedMatcher.create(matcher);
315   }
316 
317   private static DestinationPortMatcher createDestinationPortMatcher(int port) {
318     return DestinationPortMatcher.create(port);
319   }
320 
321   private static DestinationPortRangeMatcher parseDestinationPortRangeMatcher(Int32Range range) {
322     return DestinationPortRangeMatcher.create(range.getStart(), range.getEnd());
323   }
324 
325   private static DestinationIpMatcher createDestinationIpMatcher(CidrRange cidrRange) {
326     return DestinationIpMatcher.create(Matchers.CidrMatcher.create(
327             resolve(cidrRange), cidrRange.getPrefixLen().getValue()));
328   }
329 
330   private static SourceIpMatcher createSourceIpMatcher(CidrRange cidrRange) {
331     return SourceIpMatcher.create(Matchers.CidrMatcher.create(
332             resolve(cidrRange), cidrRange.getPrefixLen().getValue()));
333   }
334 
335   private static InetAddress resolve(CidrRange cidrRange) {
336     try {
337       return InetAddress.getByName(cidrRange.getAddressPrefix());
338     } catch (UnknownHostException ex) {
339       throw new IllegalArgumentException("IP address can not be found: " + ex);
340     }
341   }
342 }
343 
344