• 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.truth.Truth.assertThat;
20 import static org.mockito.ArgumentMatchers.any;
21 import static org.mockito.ArgumentMatchers.eq;
22 import static org.mockito.Mockito.mock;
23 import static org.mockito.Mockito.never;
24 import static org.mockito.Mockito.verify;
25 import static org.mockito.Mockito.verifyNoMoreInteractions;
26 import static org.mockito.Mockito.when;
27 
28 import com.google.api.expr.v1alpha1.Expr;
29 import com.google.protobuf.Any;
30 import com.google.protobuf.Message;
31 import com.google.protobuf.UInt32Value;
32 import io.envoyproxy.envoy.config.core.v3.CidrRange;
33 import io.envoyproxy.envoy.config.rbac.v3.Permission;
34 import io.envoyproxy.envoy.config.rbac.v3.Policy;
35 import io.envoyproxy.envoy.config.rbac.v3.Principal;
36 import io.envoyproxy.envoy.config.rbac.v3.Principal.Authenticated;
37 import io.envoyproxy.envoy.config.rbac.v3.RBAC;
38 import io.envoyproxy.envoy.config.rbac.v3.RBAC.Action;
39 import io.envoyproxy.envoy.config.route.v3.HeaderMatcher;
40 import io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBACPerRoute;
41 import io.envoyproxy.envoy.type.matcher.v3.MetadataMatcher;
42 import io.envoyproxy.envoy.type.matcher.v3.PathMatcher;
43 import io.envoyproxy.envoy.type.matcher.v3.StringMatcher;
44 import io.envoyproxy.envoy.type.v3.Int32Range;
45 import io.grpc.Attributes;
46 import io.grpc.Grpc;
47 import io.grpc.Metadata;
48 import io.grpc.MethodDescriptor;
49 import io.grpc.MethodDescriptor.MethodType;
50 import io.grpc.ServerCall;
51 import io.grpc.ServerCallHandler;
52 import io.grpc.ServerInterceptor;
53 import io.grpc.Status;
54 import io.grpc.testing.TestMethodDescriptors;
55 import io.grpc.xds.Filter.FilterConfig;
56 import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine;
57 import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AlwaysTrueMatcher;
58 import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AuthConfig;
59 import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AuthDecision;
60 import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.DestinationPortMatcher;
61 import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.OrMatcher;
62 import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.PolicyMatcher;
63 import java.net.InetSocketAddress;
64 import java.security.cert.X509Certificate;
65 import java.util.Arrays;
66 import java.util.Collections;
67 import java.util.List;
68 import javax.net.ssl.SSLSession;
69 import org.junit.Test;
70 import org.junit.runner.RunWith;
71 import org.junit.runners.JUnit4;
72 import org.mockito.ArgumentCaptor;
73 
74 /** Tests for {@link RbacFilter}. */
75 @RunWith(JUnit4.class)
76 public class RbacFilterTest {
77   private static final String PATH = "auth";
78   private static final StringMatcher STRING_MATCHER =
79           StringMatcher.newBuilder().setExact("/" + PATH).setIgnoreCase(true).build();
80 
81   @Test
82   @SuppressWarnings({"unchecked", "deprecation"})
ipPortParser()83   public void ipPortParser() {
84     CidrRange cidrRange = CidrRange.newBuilder().setAddressPrefix("10.10.10.0")
85             .setPrefixLen(UInt32Value.of(24)).build();
86     List<Permission> permissionList = Arrays.asList(
87             Permission.newBuilder().setAndRules(Permission.Set.newBuilder()
88                     .addRules(Permission.newBuilder().setDestinationIp(cidrRange).build())
89                     .addRules(Permission.newBuilder().setDestinationPort(9090).build()).build()
90             ).build());
91     List<Principal> principalList = Arrays.asList(
92             Principal.newBuilder().setAndIds(Principal.Set.newBuilder()
93                     .addIds(Principal.newBuilder().setDirectRemoteIp(cidrRange).build())
94                     .addIds(Principal.newBuilder().setRemoteIp(cidrRange).build())
95                     .addIds(Principal.newBuilder().setSourceIp(cidrRange).build())
96                     .build()).build());
97     ConfigOrError<?> result = parseRaw(permissionList, principalList);
98     assertThat(result.errorDetail).isNull();
99     ServerCall<Void,Void> serverCall = mock(ServerCall.class);
100     Attributes attributes = Attributes.newBuilder()
101             .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, new InetSocketAddress("10.10.10.0", 1))
102             .set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, new InetSocketAddress("10.10.10.0",9090))
103             .build();
104     when(serverCall.getAttributes()).thenReturn(attributes);
105     when(serverCall.getMethodDescriptor()).thenReturn(method().build());
106     GrpcAuthorizationEngine engine =
107             new GrpcAuthorizationEngine(((RbacConfig)result.config).authConfig());
108     AuthDecision decision = engine.evaluate(new Metadata(), serverCall);
109     assertThat(decision.decision()).isEqualTo(GrpcAuthorizationEngine.Action.DENY);
110   }
111 
112   @Test
113   @SuppressWarnings({"unchecked", "deprecation"})
portRangeParser()114   public void portRangeParser() {
115     List<Permission> permissionList = Arrays.asList(
116         Permission.newBuilder().setDestinationPortRange(
117             Int32Range.newBuilder().setStart(1010).setEnd(65535).build()
118         ).build());
119     List<Principal> principalList = Arrays.asList(
120         Principal.newBuilder().setRemoteIp(
121             CidrRange.newBuilder().setAddressPrefix("10.10.10.0")
122                 .setPrefixLen(UInt32Value.of(24)).build()
123         ).build());
124     ConfigOrError<?> result = parse(permissionList, principalList);
125     assertThat(result.errorDetail).isNull();
126     ServerCall<Void,Void> serverCall = mock(ServerCall.class);
127     Attributes attributes = Attributes.newBuilder()
128         .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, new InetSocketAddress("10.10.10.0", 1))
129         .set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, new InetSocketAddress("10.10.10.0",9090))
130         .build();
131     when(serverCall.getAttributes()).thenReturn(attributes);
132     when(serverCall.getMethodDescriptor()).thenReturn(method().build());
133     GrpcAuthorizationEngine engine =
134         new GrpcAuthorizationEngine(((RbacConfig)result.config).authConfig());
135     AuthDecision decision = engine.evaluate(new Metadata(), serverCall);
136     assertThat(decision.decision()).isEqualTo(GrpcAuthorizationEngine.Action.DENY);
137   }
138 
139   @Test
140   @SuppressWarnings("unchecked")
pathParser()141   public void pathParser() {
142     PathMatcher pathMatcher = PathMatcher.newBuilder().setPath(STRING_MATCHER).build();
143     List<Permission> permissionList = Arrays.asList(
144             Permission.newBuilder().setUrlPath(pathMatcher).build());
145     List<Principal> principalList = Arrays.asList(
146             Principal.newBuilder().setUrlPath(pathMatcher).build());
147     ConfigOrError<RbacConfig> result = parse(permissionList, principalList);
148     assertThat(result.errorDetail).isNull();
149     ServerCall<Void,Void> serverCall = mock(ServerCall.class);
150     when(serverCall.getMethodDescriptor()).thenReturn(method().build());
151     GrpcAuthorizationEngine engine =
152             new GrpcAuthorizationEngine(result.config.authConfig());
153     AuthDecision decision = engine.evaluate(new Metadata(), serverCall);
154     assertThat(decision.decision()).isEqualTo(GrpcAuthorizationEngine.Action.DENY);
155   }
156 
157   @Test
158   @SuppressWarnings("unchecked")
authenticatedParser()159   public void authenticatedParser() throws Exception {
160     List<Permission> permissionList = Arrays.asList(
161             Permission.newBuilder().setNotRule(
162                Permission.newBuilder().setRequestedServerName(STRING_MATCHER).build()).build());
163     List<Principal> principalList = Arrays.asList(
164             Principal.newBuilder().setAuthenticated(Authenticated.newBuilder()
165                     .setPrincipalName(STRING_MATCHER).build()).build());
166     ConfigOrError<?> result = parse(permissionList, principalList);
167     assertThat(result.errorDetail).isNull();
168     SSLSession sslSession = mock(SSLSession.class);
169     X509Certificate mockCert = mock(X509Certificate.class);
170     when(sslSession.getPeerCertificates()).thenReturn(new X509Certificate[]{mockCert});
171     when(mockCert.getSubjectAlternativeNames()).thenReturn(
172             Arrays.<List<?>>asList(Arrays.asList(2, "/" + PATH)));
173     Attributes attributes = Attributes.newBuilder()
174             .set(Grpc.TRANSPORT_ATTR_SSL_SESSION, sslSession)
175             .build();
176     ServerCall<Void,Void> serverCall = mock(ServerCall.class);
177     when(serverCall.getAttributes()).thenReturn(attributes);
178     GrpcAuthorizationEngine engine =
179             new GrpcAuthorizationEngine(((RbacConfig)result.config).authConfig());
180     AuthDecision decision = engine.evaluate(new Metadata(), serverCall);
181     assertThat(decision.decision()).isEqualTo(GrpcAuthorizationEngine.Action.DENY);
182   }
183 
184   @Test
185   @SuppressWarnings({"unchecked", "deprecation"})
headerParser()186   public void headerParser() {
187     HeaderMatcher headerMatcher = HeaderMatcher.newBuilder()
188             .setName("party").setExactMatch("win").build();
189     List<Permission> permissionList = Arrays.asList(
190             Permission.newBuilder().setHeader(headerMatcher).build());
191     List<Principal> principalList = Arrays.asList(
192             Principal.newBuilder().setHeader(headerMatcher).build());
193     ConfigOrError<RbacConfig> result = parseOverride(permissionList, principalList);
194     assertThat(result.errorDetail).isNull();
195     ServerCall<Void,Void> serverCall = mock(ServerCall.class);
196     GrpcAuthorizationEngine engine =
197             new GrpcAuthorizationEngine(result.config.authConfig());
198     AuthDecision decision = engine.evaluate(metadata("party", "win"), serverCall);
199     assertThat(decision.decision()).isEqualTo(GrpcAuthorizationEngine.Action.DENY);
200   }
201 
202   @Test
203   @SuppressWarnings("deprecation")
headerParser_headerName()204   public void headerParser_headerName() {
205     HeaderMatcher headerMatcher = HeaderMatcher.newBuilder()
206         .setName("grpc--feature").setExactMatch("win").build();
207     List<Permission> permissionList = Arrays.asList(
208         Permission.newBuilder().setHeader(headerMatcher).build());
209     HeaderMatcher headerMatcher2 = HeaderMatcher.newBuilder()
210         .setName(":scheme").setExactMatch("win").build();
211     List<Principal> principalList = Arrays.asList(
212         Principal.newBuilder().setHeader(headerMatcher2).build());
213     ConfigOrError<RbacConfig> result = parseOverride(permissionList, principalList);
214     assertThat(result.errorDetail).isNotNull();
215   }
216 
217   @Test
218   @SuppressWarnings("unchecked")
compositeRules()219   public void compositeRules() {
220     MetadataMatcher metadataMatcher = MetadataMatcher.newBuilder().build();
221     List<Permission> permissionList = Arrays.asList(
222             Permission.newBuilder().setOrRules(Permission.Set.newBuilder().addRules(
223                     Permission.newBuilder().setMetadata(metadataMatcher).build()
224             ).build()).build());
225     List<Principal> principalList = Arrays.asList(
226             Principal.newBuilder().setNotId(
227                     Principal.newBuilder().setMetadata(metadataMatcher).build()
228             ).build());
229     ConfigOrError<? extends FilterConfig> result = parse(permissionList, principalList);
230     assertThat(result.errorDetail).isNull();
231     assertThat(result.config).isInstanceOf(RbacConfig.class);
232     ServerCall<Void,Void> serverCall = mock(ServerCall.class);
233     GrpcAuthorizationEngine engine =
234             new GrpcAuthorizationEngine(((RbacConfig)result.config).authConfig());
235     AuthDecision decision = engine.evaluate(new Metadata(), serverCall);
236     assertThat(decision.decision()).isEqualTo(GrpcAuthorizationEngine.Action.ALLOW);
237   }
238 
239   @SuppressWarnings("unchecked")
240   @Test
testAuthorizationInterceptor()241   public void testAuthorizationInterceptor() {
242     ServerCallHandler<Void, Void> mockHandler = mock(ServerCallHandler.class);
243     ServerCall<Void, Void> mockServerCall = mock(ServerCall.class);
244     Attributes attr = Attributes.newBuilder()
245             .set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, new InetSocketAddress("1::", 20))
246             .build();
247     when(mockServerCall.getAttributes()).thenReturn(attr);
248     PolicyMatcher policyMatcher = PolicyMatcher.create("policy-matcher",
249             OrMatcher.create(DestinationPortMatcher.create(99999)),
250             OrMatcher.create(AlwaysTrueMatcher.INSTANCE));
251     AuthConfig authconfig = AuthConfig.create(Collections.singletonList(policyMatcher),
252             GrpcAuthorizationEngine.Action.ALLOW);
253     new RbacFilter().buildServerInterceptor(RbacConfig.create(authconfig), null)
254             .interceptCall(mockServerCall, new Metadata(), mockHandler);
255     verify(mockHandler, never()).startCall(eq(mockServerCall), any(Metadata.class));
256     ArgumentCaptor<Status> captor = ArgumentCaptor.forClass(Status.class);
257     verify(mockServerCall).close(captor.capture(), any(Metadata.class));
258     assertThat(captor.getValue().getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
259     assertThat(captor.getValue().getDescription()).isEqualTo("Access Denied");
260     verify(mockServerCall).getAttributes();
261     verifyNoMoreInteractions(mockServerCall);
262 
263     authconfig = AuthConfig.create(Collections.singletonList(policyMatcher),
264             GrpcAuthorizationEngine.Action.DENY);
265     new RbacFilter().buildServerInterceptor(RbacConfig.create(authconfig), null)
266             .interceptCall(mockServerCall, new Metadata(), mockHandler);
267     verify(mockHandler).startCall(eq(mockServerCall), any(Metadata.class));
268   }
269 
270   @Test
handleException()271   public void handleException() {
272     PathMatcher pathMatcher = PathMatcher.newBuilder()
273             .setPath(StringMatcher.newBuilder().build()).build();
274     List<Permission> permissionList = Arrays.asList(
275             Permission.newBuilder().setUrlPath(pathMatcher).build());
276     List<Principal> principalList = Arrays.asList(
277             Principal.newBuilder().setUrlPath(pathMatcher).build());
278     ConfigOrError<?> result = parse(permissionList, principalList);
279     assertThat(result.errorDetail).isNotNull();
280 
281     permissionList = Arrays.asList(Permission.newBuilder().build());
282     principalList = Arrays.asList(Principal.newBuilder().build());
283     result = parse(permissionList, principalList);
284     assertThat(result.errorDetail).isNotNull();
285 
286     Message rawProto = io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC.newBuilder()
287             .setRules(RBAC.newBuilder().setAction(Action.DENY)
288                     .putPolicies("policy-name",
289                             Policy.newBuilder().setCondition(Expr.newBuilder().build()).build())
290                     .build()).build();
291     result = new RbacFilter().parseFilterConfig(Any.pack(rawProto));
292     assertThat(result.errorDetail).isNotNull();
293   }
294 
295   @Test
296   @SuppressWarnings("unchecked")
overrideConfig()297   public void overrideConfig() {
298     ServerCallHandler<Void, Void> mockHandler = mock(ServerCallHandler.class);
299     ServerCall<Void, Void> mockServerCall = mock(ServerCall.class);
300     Attributes attr = Attributes.newBuilder()
301             .set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, new InetSocketAddress("1::", 20))
302             .build();
303     when(mockServerCall.getAttributes()).thenReturn(attr);
304 
305     PolicyMatcher policyMatcher = PolicyMatcher.create("policy-matcher",
306             OrMatcher.create(DestinationPortMatcher.create(99999)),
307             OrMatcher.create(AlwaysTrueMatcher.INSTANCE));
308     AuthConfig authconfig =  AuthConfig.create(Collections.singletonList(policyMatcher),
309             GrpcAuthorizationEngine.Action.ALLOW);
310     RbacConfig original = RbacConfig.create(authconfig);
311 
312     RBACPerRoute rbacPerRoute = RBACPerRoute.newBuilder().build();
313     RbacConfig override =
314             new RbacFilter().parseFilterConfigOverride(Any.pack(rbacPerRoute)).config;
315     assertThat(override).isEqualTo(RbacConfig.create(null));
316     ServerInterceptor interceptor = new RbacFilter().buildServerInterceptor(original, override);
317     assertThat(interceptor).isNull();
318 
319     policyMatcher = PolicyMatcher.create("policy-matcher-override",
320             OrMatcher.create(DestinationPortMatcher.create(20)),
321             OrMatcher.create(AlwaysTrueMatcher.INSTANCE));
322     authconfig =  AuthConfig.create(Collections.singletonList(policyMatcher),
323             GrpcAuthorizationEngine.Action.ALLOW);
324     override = RbacConfig.create(authconfig);
325 
326     new RbacFilter().buildServerInterceptor(original, override)
327             .interceptCall(mockServerCall, new Metadata(), mockHandler);
328     verify(mockHandler).startCall(eq(mockServerCall), any(Metadata.class));
329     verify(mockServerCall).getAttributes();
330     verifyNoMoreInteractions(mockServerCall);
331   }
332 
333   @Test
ignoredConfig()334   public void ignoredConfig() {
335     Message rawProto = io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC.newBuilder()
336             .setRules(RBAC.newBuilder().setAction(Action.LOG)
337                     .putPolicies("policy-name", Policy.newBuilder().build()).build()).build();
338     ConfigOrError<RbacConfig> result = new RbacFilter().parseFilterConfig(Any.pack(rawProto));
339     assertThat(result.config).isEqualTo(RbacConfig.create(null));
340   }
341 
metadata(String key, String value)342   private static Metadata metadata(String key, String value) {
343     Metadata metadata = new Metadata();
344     metadata.put(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER), value);
345     return metadata;
346   }
347 
method()348   private MethodDescriptor.Builder<Void, Void> method() {
349     return MethodDescriptor.<Void,Void>newBuilder()
350             .setType(MethodType.BIDI_STREAMING)
351             .setFullMethodName(PATH)
352             .setRequestMarshaller(TestMethodDescriptors.voidMarshaller())
353             .setResponseMarshaller(TestMethodDescriptors.voidMarshaller());
354   }
355 
parse(List<Permission> permissionList, List<Principal> principalList)356   private ConfigOrError<RbacConfig> parse(List<Permission> permissionList,
357                                                       List<Principal> principalList) {
358 
359     return RbacFilter.parseRbacConfig(buildRbac(permissionList, principalList));
360   }
361 
parseRaw(List<Permission> permissionList, List<Principal> principalList)362   private ConfigOrError<RbacConfig> parseRaw(List<Permission> permissionList,
363                                                       List<Principal> principalList) {
364     Message rawProto = buildRbac(permissionList, principalList);
365     Any proto = Any.pack(rawProto);
366     return new RbacFilter().parseFilterConfig(proto);
367   }
368 
buildRbac( List<Permission> permissionList, List<Principal> principalList)369   private io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC buildRbac(
370           List<Permission> permissionList, List<Principal> principalList) {
371     return io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC.newBuilder()
372             .setRules(RBAC.newBuilder().setAction(Action.DENY)
373                     .putPolicies("policy-name", Policy.newBuilder()
374                             .addAllPermissions(permissionList)
375                             .addAllPrincipals(principalList).build()).build()).build();
376 
377   }
378 
parseOverride(List<Permission> permissionList, List<Principal> principalList)379   private ConfigOrError<RbacConfig> parseOverride(List<Permission> permissionList,
380                                              List<Principal> principalList) {
381     RBACPerRoute rbacPerRoute = RBACPerRoute.newBuilder().setRbac(
382             buildRbac(permissionList, principalList)).build();
383     Any proto = Any.pack(rbacPerRoute);
384     return new RbacFilter().parseFilterConfigOverride(proto);
385   }
386 }
387