mirror of https://github.com/grpc/grpc-java.git
xds: implement RBAC gRFC misc cases (#8518)
This commit is contained in:
parent
fcf13952bb
commit
38a554c23a
|
|
@ -136,6 +136,10 @@ final class ClientXdsClient extends AbstractXdsClient {
|
||||||
static boolean enableRetry =
|
static boolean enableRetry =
|
||||||
Strings.isNullOrEmpty(System.getenv("GRPC_XDS_EXPERIMENTAL_ENABLE_RETRY"))
|
Strings.isNullOrEmpty(System.getenv("GRPC_XDS_EXPERIMENTAL_ENABLE_RETRY"))
|
||||||
|| Boolean.parseBoolean(System.getenv("GRPC_XDS_EXPERIMENTAL_ENABLE_RETRY"));
|
|| Boolean.parseBoolean(System.getenv("GRPC_XDS_EXPERIMENTAL_ENABLE_RETRY"));
|
||||||
|
@VisibleForTesting
|
||||||
|
static boolean enableRbac =
|
||||||
|
Strings.isNullOrEmpty(System.getenv("GRPC_XDS_EXPERIMENTAL_RBAC"))
|
||||||
|
|| Boolean.parseBoolean(System.getenv("GRPC_XDS_EXPERIMENTAL_RBAC"));
|
||||||
|
|
||||||
private static final String TYPE_URL_HTTP_CONNECTION_MANAGER_V2 =
|
private static final String TYPE_URL_HTTP_CONNECTION_MANAGER_V2 =
|
||||||
"type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2"
|
"type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2"
|
||||||
|
|
@ -218,7 +222,7 @@ final class ClientXdsClient extends AbstractXdsClient {
|
||||||
listener, retainedRdsResources, enableFaultInjection && isResourceV3);
|
listener, retainedRdsResources, enableFaultInjection && isResourceV3);
|
||||||
} else {
|
} else {
|
||||||
ldsUpdate = processServerSideListener(
|
ldsUpdate = processServerSideListener(
|
||||||
listener, retainedRdsResources, enableFaultInjection && isResourceV3);
|
listener, retainedRdsResources, enableRbac);
|
||||||
}
|
}
|
||||||
} catch (ResourceInvalidException e) {
|
} catch (ResourceInvalidException e) {
|
||||||
errors.add(
|
errors.add(
|
||||||
|
|
@ -729,10 +733,14 @@ final class ClientXdsClient extends AbstractXdsClient {
|
||||||
static io.grpc.xds.HttpConnectionManager parseHttpConnectionManager(
|
static io.grpc.xds.HttpConnectionManager parseHttpConnectionManager(
|
||||||
HttpConnectionManager proto, Set<String> rdsResources, FilterRegistry filterRegistry,
|
HttpConnectionManager proto, Set<String> rdsResources, FilterRegistry filterRegistry,
|
||||||
boolean parseHttpFilter, boolean isForClient) throws ResourceInvalidException {
|
boolean parseHttpFilter, boolean isForClient) throws ResourceInvalidException {
|
||||||
if (proto.getXffNumTrustedHops() != 0) {
|
if (enableRbac && proto.getXffNumTrustedHops() != 0) {
|
||||||
throw new ResourceInvalidException(
|
throw new ResourceInvalidException(
|
||||||
"HttpConnectionManager with xff_num_trusted_hops unsupported");
|
"HttpConnectionManager with xff_num_trusted_hops unsupported");
|
||||||
}
|
}
|
||||||
|
if (enableRbac && !proto.getOriginalIpDetectionExtensionsList().isEmpty()) {
|
||||||
|
throw new ResourceInvalidException("HttpConnectionManager with "
|
||||||
|
+ "original_ip_detection_extensions unsupported");
|
||||||
|
}
|
||||||
// Obtain max_stream_duration from Http Protocol Options.
|
// Obtain max_stream_duration from Http Protocol Options.
|
||||||
long maxStreamDuration = 0;
|
long maxStreamDuration = 0;
|
||||||
if (proto.hasCommonHttpProtocolOptions()) {
|
if (proto.hasCommonHttpProtocolOptions()) {
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import io.envoyproxy.envoy.config.rbac.v3.Policy;
|
||||||
import io.envoyproxy.envoy.config.rbac.v3.Principal;
|
import io.envoyproxy.envoy.config.rbac.v3.Principal;
|
||||||
import io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC;
|
import io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC;
|
||||||
import io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBACPerRoute;
|
import io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBACPerRoute;
|
||||||
|
import io.envoyproxy.envoy.type.v3.Int32Range;
|
||||||
import io.grpc.Metadata;
|
import io.grpc.Metadata;
|
||||||
import io.grpc.ServerCall;
|
import io.grpc.ServerCall;
|
||||||
import io.grpc.ServerCallHandler;
|
import io.grpc.ServerCallHandler;
|
||||||
|
|
@ -45,6 +46,7 @@ import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AuthHeaderMatche
|
||||||
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AuthenticatedMatcher;
|
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AuthenticatedMatcher;
|
||||||
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.DestinationIpMatcher;
|
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.DestinationIpMatcher;
|
||||||
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.DestinationPortMatcher;
|
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.DestinationPortMatcher;
|
||||||
|
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.DestinationPortRangeMatcher;
|
||||||
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.InvertMatcher;
|
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.InvertMatcher;
|
||||||
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.Matcher;
|
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.Matcher;
|
||||||
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.OrMatcher;
|
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.OrMatcher;
|
||||||
|
|
@ -216,6 +218,8 @@ final class RbacFilter implements Filter, ServerInterceptorBuilder {
|
||||||
return createDestinationIpMatcher(permission.getDestinationIp());
|
return createDestinationIpMatcher(permission.getDestinationIp());
|
||||||
case DESTINATION_PORT:
|
case DESTINATION_PORT:
|
||||||
return createDestinationPortMatcher(permission.getDestinationPort());
|
return createDestinationPortMatcher(permission.getDestinationPort());
|
||||||
|
case DESTINATION_PORT_RANGE:
|
||||||
|
return parseDestinationPortRangeMatcher(permission.getDestinationPortRange());
|
||||||
case NOT_RULE:
|
case NOT_RULE:
|
||||||
return new InvertMatcher(parsePermission(permission.getNotRule()));
|
return new InvertMatcher(parsePermission(permission.getNotRule()));
|
||||||
case METADATA: // hard coded, never match.
|
case METADATA: // hard coded, never match.
|
||||||
|
|
@ -291,6 +295,14 @@ final class RbacFilter implements Filter, ServerInterceptorBuilder {
|
||||||
|
|
||||||
private static AuthHeaderMatcher parseHeaderMatcher(
|
private static AuthHeaderMatcher parseHeaderMatcher(
|
||||||
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto) {
|
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto) {
|
||||||
|
if (proto.getName().startsWith("grpc-")) {
|
||||||
|
throw new IllegalArgumentException("Invalid header matcher config: [grpc-] prefixed "
|
||||||
|
+ "header name is not allowed.");
|
||||||
|
}
|
||||||
|
if (":scheme".equals(proto.getName())) {
|
||||||
|
throw new IllegalArgumentException("Invalid header matcher config: header name [:scheme] "
|
||||||
|
+ "is not allowed.");
|
||||||
|
}
|
||||||
return new AuthHeaderMatcher(MatcherParser.parseHeaderMatcher(proto));
|
return new AuthHeaderMatcher(MatcherParser.parseHeaderMatcher(proto));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -304,6 +316,10 @@ final class RbacFilter implements Filter, ServerInterceptorBuilder {
|
||||||
return new DestinationPortMatcher(port);
|
return new DestinationPortMatcher(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static DestinationPortRangeMatcher parseDestinationPortRangeMatcher(Int32Range range) {
|
||||||
|
return new DestinationPortRangeMatcher(range.getStart(), range.getEnd());
|
||||||
|
}
|
||||||
|
|
||||||
private static DestinationIpMatcher createDestinationIpMatcher(CidrRange cidrRange) {
|
private static DestinationIpMatcher createDestinationIpMatcher(CidrRange cidrRange) {
|
||||||
return new DestinationIpMatcher(Matchers.CidrMatcher.create(
|
return new DestinationIpMatcher(Matchers.CidrMatcher.create(
|
||||||
resolve(cidrRange), cidrRange.getPrefixLen().getValue()));
|
resolve(cidrRange), cidrRange.getPrefixLen().getValue()));
|
||||||
|
|
|
||||||
|
|
@ -639,6 +639,9 @@ final class XdsServerWrapper extends Server {
|
||||||
if (!routeDiscoveryStates.containsKey(resourceName)) {
|
if (!routeDiscoveryStates.containsKey(resourceName)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (savedVirtualHosts == null && !isPending) {
|
||||||
|
logger.log(Level.WARNING, "Received valid Rds {0} configuration.", resourceName);
|
||||||
|
}
|
||||||
savedVirtualHosts = ImmutableList.copyOf(update.virtualHosts);
|
savedVirtualHosts = ImmutableList.copyOf(update.virtualHosts);
|
||||||
updateRdsRoutingConfig();
|
updateRdsRoutingConfig();
|
||||||
maybeUpdateSelector();
|
maybeUpdateSelector();
|
||||||
|
|
@ -760,11 +763,15 @@ final class XdsServerWrapper extends Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (selectedRoute == null) {
|
if (selectedRoute == null) {
|
||||||
call.close(
|
call.close(Status.UNAVAILABLE.withDescription("Could not find xDS route matching RPC"),
|
||||||
Status.UNAVAILABLE.withDescription("Could not find xDS route matching RPC"),
|
|
||||||
new Metadata());
|
new Metadata());
|
||||||
return new ServerCall.Listener<ReqT>() {};
|
return new ServerCall.Listener<ReqT>() {};
|
||||||
}
|
}
|
||||||
|
if (selectedRoute.routeAction() != null) {
|
||||||
|
call.close(Status.UNAVAILABLE.withDescription("Invalid xDS route action for matching "
|
||||||
|
+ "route: only Route.non_forwarding_action should be allowed."), new Metadata());
|
||||||
|
return new ServerCall.Listener<ReqT>() {};
|
||||||
|
}
|
||||||
ServerInterceptor routeInterceptor = noopInterceptor;
|
ServerInterceptor routeInterceptor = noopInterceptor;
|
||||||
Map<Route, ServerInterceptor> perRouteInterceptors = routingConfig.interceptors();
|
Map<Route, ServerInterceptor> perRouteInterceptors = routingConfig.interceptors();
|
||||||
if (perRouteInterceptors != null && perRouteInterceptors.get(selectedRoute) != null) {
|
if (perRouteInterceptors != null && perRouteInterceptors.get(selectedRoute) != null) {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
import com.google.auto.value.AutoValue;
|
import com.google.auto.value.AutoValue;
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
|
import com.google.common.io.BaseEncoding;
|
||||||
import io.grpc.Grpc;
|
import io.grpc.Grpc;
|
||||||
import io.grpc.Metadata;
|
import io.grpc.Metadata;
|
||||||
import io.grpc.ServerCall;
|
import io.grpc.ServerCall;
|
||||||
|
|
@ -35,6 +36,7 @@ import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
@ -234,6 +236,23 @@ public final class GrpcAuthorizationEngine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class DestinationPortRangeMatcher implements Matcher {
|
||||||
|
private final int start;
|
||||||
|
private final int end;
|
||||||
|
|
||||||
|
/** Start of the range is inclusive. End of the range is exclusive.*/
|
||||||
|
public DestinationPortRangeMatcher(int start, int end) {
|
||||||
|
this.start = start;
|
||||||
|
this.end = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(EvaluateArgs args) {
|
||||||
|
int port = args.getDestinationPort();
|
||||||
|
return port >= start && port < end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static final class RequestedServerNameMatcher implements Matcher {
|
public static final class RequestedServerNameMatcher implements Matcher {
|
||||||
private final Matchers.StringMatcher delegate;
|
private final Matchers.StringMatcher delegate;
|
||||||
|
|
||||||
|
|
@ -316,9 +335,44 @@ public final class GrpcAuthorizationEngine {
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private String getHeader(String headerName) {
|
private String getHeader(String headerName) {
|
||||||
if (headerName.endsWith(Metadata.BINARY_HEADER_SUFFIX)) {
|
headerName = headerName.toLowerCase(Locale.ROOT);
|
||||||
|
if ("te".equals(headerName)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (":authority".equals(headerName)) {
|
||||||
|
headerName = "host";
|
||||||
|
}
|
||||||
|
if ("host".equals(headerName)) {
|
||||||
|
return serverCall.getAuthority();
|
||||||
|
}
|
||||||
|
if (":path".equals(headerName)) {
|
||||||
|
return getPath();
|
||||||
|
}
|
||||||
|
if (":method".equals(headerName)) {
|
||||||
|
return "POST";
|
||||||
|
}
|
||||||
|
return deserializeHeader(headerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String deserializeHeader(String headerName) {
|
||||||
|
if (headerName.endsWith(Metadata.BINARY_HEADER_SUFFIX)) {
|
||||||
|
Metadata.Key<byte[]> key;
|
||||||
|
try {
|
||||||
|
key = Metadata.Key.of(headerName, Metadata.BINARY_BYTE_MARSHALLER);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Iterable<byte[]> values = metadata.getAll(key);
|
||||||
|
if (values == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
List<String> encoded = new ArrayList<>();
|
||||||
|
for (byte[] v : values) {
|
||||||
|
encoded.add(BaseEncoding.base64().omitPadding().encode(v));
|
||||||
|
}
|
||||||
|
return Joiner.on(",").join(encoded);
|
||||||
|
}
|
||||||
Metadata.Key<String> key;
|
Metadata.Key<String> key;
|
||||||
try {
|
try {
|
||||||
key = Metadata.Key.of(headerName, Metadata.ASCII_STRING_MARSHALLER);
|
key = Metadata.Key.of(headerName, Metadata.ASCII_STRING_MARSHALLER);
|
||||||
|
|
|
||||||
|
|
@ -131,16 +131,20 @@ public class ClientXdsClientDataTest {
|
||||||
public final ExpectedException thrown = ExpectedException.none();
|
public final ExpectedException thrown = ExpectedException.none();
|
||||||
private final FilterRegistry filterRegistry = FilterRegistry.getDefaultRegistry();
|
private final FilterRegistry filterRegistry = FilterRegistry.getDefaultRegistry();
|
||||||
private boolean originalEnableRetry;
|
private boolean originalEnableRetry;
|
||||||
|
private boolean originalEnableRbac;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
originalEnableRetry = ClientXdsClient.enableRetry;
|
originalEnableRetry = ClientXdsClient.enableRetry;
|
||||||
assertThat(originalEnableRetry).isTrue();
|
assertThat(originalEnableRetry).isTrue();
|
||||||
|
originalEnableRbac = ClientXdsClient.enableRbac;
|
||||||
|
assertThat(originalEnableRbac).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
ClientXdsClient.enableRetry = originalEnableRetry;
|
ClientXdsClient.enableRetry = originalEnableRetry;
|
||||||
|
ClientXdsClient.enableRbac = originalEnableRbac;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -1109,6 +1113,19 @@ public class ClientXdsClientDataTest {
|
||||||
true /* does not matter */);
|
true /* does not matter */);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseHttpConnectionManager_OriginalIpDetectionExtensionsMustEmpty()
|
||||||
|
throws ResourceInvalidException {
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
HttpConnectionManager hcm = HttpConnectionManager.newBuilder()
|
||||||
|
.addOriginalIpDetectionExtensions(TypedExtensionConfig.newBuilder().build())
|
||||||
|
.build();
|
||||||
|
thrown.expect(ResourceInvalidException.class);
|
||||||
|
thrown.expectMessage("HttpConnectionManager with original_ip_detection_extensions unsupported");
|
||||||
|
ClientXdsClient.parseHttpConnectionManager(
|
||||||
|
hcm, new HashSet<String>(), filterRegistry, false /* does not matter */, false);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parseHttpConnectionManager_missingRdsAndInlinedRouteConfiguration()
|
public void parseHttpConnectionManager_missingRdsAndInlinedRouteConfiguration()
|
||||||
throws ResourceInvalidException {
|
throws ResourceInvalidException {
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ import io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBACPerRoute;
|
||||||
import io.envoyproxy.envoy.type.matcher.v3.MetadataMatcher;
|
import io.envoyproxy.envoy.type.matcher.v3.MetadataMatcher;
|
||||||
import io.envoyproxy.envoy.type.matcher.v3.PathMatcher;
|
import io.envoyproxy.envoy.type.matcher.v3.PathMatcher;
|
||||||
import io.envoyproxy.envoy.type.matcher.v3.StringMatcher;
|
import io.envoyproxy.envoy.type.matcher.v3.StringMatcher;
|
||||||
|
import io.envoyproxy.envoy.type.v3.Int32Range;
|
||||||
import io.grpc.Attributes;
|
import io.grpc.Attributes;
|
||||||
import io.grpc.Grpc;
|
import io.grpc.Grpc;
|
||||||
import io.grpc.Metadata;
|
import io.grpc.Metadata;
|
||||||
|
|
@ -109,6 +110,33 @@ public class RbacFilterTest {
|
||||||
assertThat(decision.decision()).isEqualTo(GrpcAuthorizationEngine.Action.DENY);
|
assertThat(decision.decision()).isEqualTo(GrpcAuthorizationEngine.Action.DENY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings({"unchecked", "deprecation"})
|
||||||
|
public void portRangeParser() {
|
||||||
|
List<Permission> permissionList = Arrays.asList(
|
||||||
|
Permission.newBuilder().setDestinationPortRange(
|
||||||
|
Int32Range.newBuilder().setStart(1010).setEnd(65535).build()
|
||||||
|
).build());
|
||||||
|
List<Principal> principalList = Arrays.asList(
|
||||||
|
Principal.newBuilder().setRemoteIp(
|
||||||
|
CidrRange.newBuilder().setAddressPrefix("10.10.10.0")
|
||||||
|
.setPrefixLen(UInt32Value.of(24)).build()
|
||||||
|
).build());
|
||||||
|
ConfigOrError<?> result = parse(permissionList, principalList);
|
||||||
|
assertThat(result.errorDetail).isNull();
|
||||||
|
ServerCall<Void,Void> serverCall = mock(ServerCall.class);
|
||||||
|
Attributes attributes = Attributes.newBuilder()
|
||||||
|
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, new InetSocketAddress("10.10.10.0", 1))
|
||||||
|
.set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, new InetSocketAddress("10.10.10.0",9090))
|
||||||
|
.build();
|
||||||
|
when(serverCall.getAttributes()).thenReturn(attributes);
|
||||||
|
when(serverCall.getMethodDescriptor()).thenReturn(method().build());
|
||||||
|
GrpcAuthorizationEngine engine =
|
||||||
|
new GrpcAuthorizationEngine(((RbacConfig)result.config).authConfig());
|
||||||
|
AuthDecision decision = engine.evaluate(new Metadata(), serverCall);
|
||||||
|
assertThat(decision.decision()).isEqualTo(GrpcAuthorizationEngine.Action.DENY);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void pathParser() {
|
public void pathParser() {
|
||||||
|
|
@ -172,6 +200,21 @@ public class RbacFilterTest {
|
||||||
assertThat(decision.decision()).isEqualTo(GrpcAuthorizationEngine.Action.DENY);
|
assertThat(decision.decision()).isEqualTo(GrpcAuthorizationEngine.Action.DENY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public void headerParser_headerName() {
|
||||||
|
HeaderMatcher headerMatcher = HeaderMatcher.newBuilder()
|
||||||
|
.setName("grpc--feature").setExactMatch("win").build();
|
||||||
|
List<Permission> permissionList = Arrays.asList(
|
||||||
|
Permission.newBuilder().setHeader(headerMatcher).build());
|
||||||
|
HeaderMatcher headerMatcher2 = HeaderMatcher.newBuilder()
|
||||||
|
.setName(":scheme").setExactMatch("win").build();
|
||||||
|
List<Principal> principalList = Arrays.asList(
|
||||||
|
Principal.newBuilder().setHeader(headerMatcher2).build());
|
||||||
|
ConfigOrError<RbacConfig> result = parseOverride(permissionList, principalList);
|
||||||
|
assertThat(result.errorDetail).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void compositeRules() {
|
public void compositeRules() {
|
||||||
|
|
|
||||||
|
|
@ -865,6 +865,50 @@ public class XdsServerWrapperTest {
|
||||||
assertThat(status.getDescription()).isEqualTo("Could not find xDS route matching RPC");
|
assertThat(status.getDescription()).isEqualTo("Could not find xDS route matching RPC");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void interceptor_invalidRouteAction() throws Exception {
|
||||||
|
ArgumentCaptor<ConfigApplyingInterceptor> interceptorCaptor =
|
||||||
|
ArgumentCaptor.forClass(ConfigApplyingInterceptor.class);
|
||||||
|
final SettableFuture<Server> start = SettableFuture.create();
|
||||||
|
Executors.newSingleThreadExecutor().execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
start.set(xdsServerWrapper.start());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
start.setException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
xdsClient.ldsResource.get(5, TimeUnit.SECONDS);
|
||||||
|
verify(mockBuilder).intercept(interceptorCaptor.capture());
|
||||||
|
ConfigApplyingInterceptor interceptor = interceptorCaptor.getValue();
|
||||||
|
ServerRoutingConfig routingConfig = createRoutingConfig("/FooService/barMethod",
|
||||||
|
"foo.google.com", "filter-type-url", Route.RouteAction.forCluster(
|
||||||
|
"cluster", Collections.<Route.RouteAction.HashPolicy>emptyList(), null, null
|
||||||
|
));
|
||||||
|
ServerCall<Void, Void> serverCall = mock(ServerCall.class);
|
||||||
|
when(serverCall.getAttributes()).thenReturn(
|
||||||
|
Attributes.newBuilder()
|
||||||
|
.set(ATTR_SERVER_ROUTING_CONFIG, new AtomicReference<>(routingConfig)).build());
|
||||||
|
when(serverCall.getMethodDescriptor()).thenReturn(createMethod("FooService/barMethod"));
|
||||||
|
when(serverCall.getAuthority()).thenReturn("foo.google.com");
|
||||||
|
|
||||||
|
Filter filter = mock(Filter.class);
|
||||||
|
when(filter.typeUrls()).thenReturn(new String[]{"filter-type-url"});
|
||||||
|
filterRegistry.register(filter);
|
||||||
|
ServerCallHandler<Void, Void> next = mock(ServerCallHandler.class);
|
||||||
|
interceptor.interceptCall(serverCall, new Metadata(), next);
|
||||||
|
verify(next, never()).startCall(any(ServerCall.class), any(Metadata.class));
|
||||||
|
ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
|
||||||
|
verify(serverCall).close(statusCaptor.capture(), any(Metadata.class));
|
||||||
|
Status status = statusCaptor.getValue();
|
||||||
|
assertThat(status.getCode()).isEqualTo(Status.UNAVAILABLE.getCode());
|
||||||
|
assertThat(status.getDescription()).isEqualTo("Invalid xDS route action for matching "
|
||||||
|
+ "route: only Route.non_forwarding_action should be allowed.");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void interceptor_failingRouterConfig() throws Exception {
|
public void interceptor_failingRouterConfig() throws Exception {
|
||||||
|
|
@ -1104,13 +1148,18 @@ public class XdsServerWrapperTest {
|
||||||
|
|
||||||
private static ServerRoutingConfig createRoutingConfig(String path, String domain,
|
private static ServerRoutingConfig createRoutingConfig(String path, String domain,
|
||||||
String filterType) {
|
String filterType) {
|
||||||
|
return createRoutingConfig(path, domain, filterType, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ServerRoutingConfig createRoutingConfig(String path, String domain,
|
||||||
|
String filterType, Route.RouteAction action) {
|
||||||
RouteMatch routeMatch =
|
RouteMatch routeMatch =
|
||||||
RouteMatch.create(
|
RouteMatch.create(
|
||||||
PathMatcher.fromPath(path, true),
|
PathMatcher.fromPath(path, true),
|
||||||
Collections.<HeaderMatcher>emptyList(), null);
|
Collections.<HeaderMatcher>emptyList(), null);
|
||||||
VirtualHost virtualHost = VirtualHost.create(
|
VirtualHost virtualHost = VirtualHost.create(
|
||||||
"v1", Collections.singletonList(domain),
|
"v1", Collections.singletonList(domain),
|
||||||
Arrays.asList(Route.forAction(routeMatch, null,
|
Arrays.asList(Route.forAction(routeMatch, action,
|
||||||
ImmutableMap.<String, FilterConfig>of())),
|
ImmutableMap.<String, FilterConfig>of())),
|
||||||
Collections.<String, FilterConfig>emptyMap());
|
Collections.<String, FilterConfig>emptyMap());
|
||||||
FilterConfig f0 = mock(FilterConfig.class);
|
FilterConfig f0 = mock(FilterConfig.class);
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,14 @@
|
||||||
|
|
||||||
package io.grpc.xds.internal.rbac.engine;
|
package io.grpc.xds.internal.rbac.engine;
|
||||||
|
|
||||||
|
import static com.google.common.base.Charsets.US_ASCII;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static org.mockito.Mockito.doThrow;
|
import static org.mockito.Mockito.doThrow;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.io.BaseEncoding;
|
||||||
import io.grpc.Attributes;
|
import io.grpc.Attributes;
|
||||||
import io.grpc.Grpc;
|
import io.grpc.Grpc;
|
||||||
import io.grpc.Metadata;
|
import io.grpc.Metadata;
|
||||||
|
|
@ -177,6 +179,71 @@ public class GrpcAuthorizationEngineTest {
|
||||||
assertThat(decision.decision()).isEqualTo(Action.DENY);
|
assertThat(decision.decision()).isEqualTo(Action.DENY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void headerMatcher_binaryHeader() {
|
||||||
|
AuthHeaderMatcher headerMatcher = new AuthHeaderMatcher(Matchers.HeaderMatcher
|
||||||
|
.forExactValue(HEADER_KEY + Metadata.BINARY_HEADER_SUFFIX,
|
||||||
|
BaseEncoding.base64().omitPadding().encode(HEADER_VALUE.getBytes(US_ASCII)), false));
|
||||||
|
OrMatcher principal = OrMatcher.create(headerMatcher);
|
||||||
|
OrMatcher permission = OrMatcher.create(
|
||||||
|
new InvertMatcher(new DestinationPortMatcher(PORT + 1)));
|
||||||
|
PolicyMatcher policyMatcher = new PolicyMatcher(POLICY_NAME, permission, principal);
|
||||||
|
GrpcAuthorizationEngine engine = new GrpcAuthorizationEngine(
|
||||||
|
new AuthConfig(Collections.singletonList(policyMatcher), Action.ALLOW));
|
||||||
|
Metadata metadata = new Metadata();
|
||||||
|
metadata.put(Metadata.Key.of(HEADER_KEY + Metadata.BINARY_HEADER_SUFFIX,
|
||||||
|
Metadata.BINARY_BYTE_MARSHALLER), HEADER_VALUE.getBytes(US_ASCII));
|
||||||
|
AuthDecision decision = engine.evaluate(metadata, serverCall);
|
||||||
|
assertThat(decision.decision()).isEqualTo(Action.ALLOW);
|
||||||
|
assertThat(decision.matchingPolicyName()).isEqualTo(POLICY_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void headerMatcher_hardcodePostMethod() {
|
||||||
|
AuthHeaderMatcher headerMatcher = new AuthHeaderMatcher(Matchers.HeaderMatcher
|
||||||
|
.forExactValue(":method", "POST", false));
|
||||||
|
OrMatcher principal = OrMatcher.create(headerMatcher);
|
||||||
|
OrMatcher permission = OrMatcher.create(
|
||||||
|
new InvertMatcher(new DestinationPortMatcher(PORT + 1)));
|
||||||
|
PolicyMatcher policyMatcher = new PolicyMatcher(POLICY_NAME, permission, principal);
|
||||||
|
GrpcAuthorizationEngine engine = new GrpcAuthorizationEngine(
|
||||||
|
new AuthConfig(Collections.singletonList(policyMatcher), Action.ALLOW));
|
||||||
|
AuthDecision decision = engine.evaluate(new Metadata(), serverCall);
|
||||||
|
assertThat(decision.decision()).isEqualTo(Action.ALLOW);
|
||||||
|
assertThat(decision.matchingPolicyName()).isEqualTo(POLICY_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void headerMatcher_pathHeader() {
|
||||||
|
AuthHeaderMatcher headerMatcher = new AuthHeaderMatcher(Matchers.HeaderMatcher
|
||||||
|
.forExactValue(":path", "/" + PATH, false));
|
||||||
|
OrMatcher principal = OrMatcher.create(headerMatcher);
|
||||||
|
OrMatcher permission = OrMatcher.create(
|
||||||
|
new InvertMatcher(new DestinationPortMatcher(PORT + 1)));
|
||||||
|
PolicyMatcher policyMatcher = new PolicyMatcher(POLICY_NAME, permission, principal);
|
||||||
|
GrpcAuthorizationEngine engine = new GrpcAuthorizationEngine(
|
||||||
|
new AuthConfig(Collections.singletonList(policyMatcher), Action.ALLOW));
|
||||||
|
AuthDecision decision = engine.evaluate(HEADER, serverCall);
|
||||||
|
assertThat(decision.decision()).isEqualTo(Action.ALLOW);
|
||||||
|
assertThat(decision.matchingPolicyName()).isEqualTo(POLICY_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void headerMatcher_aliasAuthorityAndHost() {
|
||||||
|
AuthHeaderMatcher headerMatcher = new AuthHeaderMatcher(Matchers.HeaderMatcher
|
||||||
|
.forExactValue("Host", "google.com", false));
|
||||||
|
OrMatcher principal = OrMatcher.create(headerMatcher);
|
||||||
|
OrMatcher permission = OrMatcher.create(
|
||||||
|
new InvertMatcher(new DestinationPortMatcher(PORT + 1)));
|
||||||
|
PolicyMatcher policyMatcher = new PolicyMatcher(POLICY_NAME, permission, principal);
|
||||||
|
GrpcAuthorizationEngine engine = new GrpcAuthorizationEngine(
|
||||||
|
new AuthConfig(Collections.singletonList(policyMatcher), Action.ALLOW));
|
||||||
|
when(serverCall.getAuthority()).thenReturn("google.com");
|
||||||
|
AuthDecision decision = engine.evaluate(new Metadata(), serverCall);
|
||||||
|
assertThat(decision.decision()).isEqualTo(Action.ALLOW);
|
||||||
|
assertThat(decision.matchingPolicyName()).isEqualTo(POLICY_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void pathMatcher() {
|
public void pathMatcher() {
|
||||||
PathMatcher pathMatcher = new PathMatcher(STRING_MATCHER);
|
PathMatcher pathMatcher = new PathMatcher(STRING_MATCHER);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue