mirror of https://github.com/grpc/grpc-java.git
xds, rbac: implement rbac engine (#8168)
This commit is contained in:
parent
d4c31ffad4
commit
b7f3fddc76
|
|
@ -66,15 +66,15 @@ import io.grpc.xds.Filter.FilterConfig;
|
|||
import io.grpc.xds.Filter.NamedFilterConfig;
|
||||
import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
|
||||
import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats;
|
||||
import io.grpc.xds.Matchers.FractionMatcher;
|
||||
import io.grpc.xds.Matchers.HeaderMatcher;
|
||||
import io.grpc.xds.Matchers.PathMatcher;
|
||||
import io.grpc.xds.VirtualHost.Route;
|
||||
import io.grpc.xds.VirtualHost.Route.RouteAction;
|
||||
import io.grpc.xds.VirtualHost.Route.RouteAction.ClusterWeight;
|
||||
import io.grpc.xds.VirtualHost.Route.RouteAction.HashPolicy;
|
||||
import io.grpc.xds.VirtualHost.Route.RouteMatch;
|
||||
import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher;
|
||||
import io.grpc.xds.XdsLogger.XdsLogLevel;
|
||||
import io.grpc.xds.internal.Matchers.FractionMatcher;
|
||||
import io.grpc.xds.internal.Matchers.HeaderMatcher;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
|
|
|||
|
|
@ -1,171 +0,0 @@
|
|||
/*
|
||||
* Copyright 2021 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.grpc.xds;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.re2j.Pattern;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** A group of request matchers. */
|
||||
final class Matchers {
|
||||
private Matchers() {}
|
||||
|
||||
/** Matcher for HTTP request path. */
|
||||
@AutoValue
|
||||
abstract static class PathMatcher {
|
||||
// Exact full path to be matched.
|
||||
@Nullable
|
||||
abstract String path();
|
||||
|
||||
// Path prefix to be matched.
|
||||
@Nullable
|
||||
abstract String prefix();
|
||||
|
||||
// Regular expression pattern of the path to be matched.
|
||||
@Nullable
|
||||
abstract Pattern regEx();
|
||||
|
||||
// Whether case sensitivity is taken into account for matching.
|
||||
// Only valid for full path matching or prefix matching.
|
||||
abstract boolean caseSensitive();
|
||||
|
||||
static PathMatcher fromPath(String path, boolean caseSensitive) {
|
||||
checkNotNull(path, "path");
|
||||
return PathMatcher.create(path, null, null, caseSensitive);
|
||||
}
|
||||
|
||||
static PathMatcher fromPrefix(String prefix, boolean caseSensitive) {
|
||||
checkNotNull(prefix, "prefix");
|
||||
return PathMatcher.create(null, prefix, null, caseSensitive);
|
||||
}
|
||||
|
||||
static PathMatcher fromRegEx(Pattern regEx) {
|
||||
checkNotNull(regEx, "regEx");
|
||||
return PathMatcher.create(null, null, regEx, false /* doesn't matter */);
|
||||
}
|
||||
|
||||
private static PathMatcher create(@Nullable String path, @Nullable String prefix,
|
||||
@Nullable Pattern regEx, boolean caseSensitive) {
|
||||
return new AutoValue_Matchers_PathMatcher(path, prefix, regEx, caseSensitive);
|
||||
}
|
||||
}
|
||||
|
||||
/** Matcher for HTTP request headers. */
|
||||
@AutoValue
|
||||
abstract static class HeaderMatcher {
|
||||
// Name of the header to be matched.
|
||||
abstract String name();
|
||||
|
||||
// Matches exact header value.
|
||||
@Nullable
|
||||
abstract String exactValue();
|
||||
|
||||
// Matches header value with the regular expression pattern.
|
||||
@Nullable
|
||||
abstract Pattern safeRegEx();
|
||||
|
||||
// Matches header value an integer value in the range.
|
||||
@Nullable
|
||||
abstract Range range();
|
||||
|
||||
// Matches header presence.
|
||||
@Nullable
|
||||
abstract Boolean present();
|
||||
|
||||
// Matches header value with the prefix.
|
||||
@Nullable
|
||||
abstract String prefix();
|
||||
|
||||
// Matches header value with the suffix.
|
||||
@Nullable
|
||||
abstract String suffix();
|
||||
|
||||
// Whether the matching semantics is inverted. E.g., present && !inverted -> !present
|
||||
abstract boolean inverted();
|
||||
|
||||
static HeaderMatcher forExactValue(String name, String exactValue, boolean inverted) {
|
||||
checkNotNull(name, "name");
|
||||
checkNotNull(exactValue, "exactValue");
|
||||
return HeaderMatcher.create(name, exactValue, null, null, null, null, null, inverted);
|
||||
}
|
||||
|
||||
static HeaderMatcher forSafeRegEx(String name, Pattern safeRegEx, boolean inverted) {
|
||||
checkNotNull(name, "name");
|
||||
checkNotNull(safeRegEx, "safeRegEx");
|
||||
return HeaderMatcher.create(name, null, safeRegEx, null, null, null, null, inverted);
|
||||
}
|
||||
|
||||
static HeaderMatcher forRange(String name, Range range, boolean inverted) {
|
||||
checkNotNull(name, "name");
|
||||
checkNotNull(range, "range");
|
||||
return HeaderMatcher.create(name, null, null, range, null, null, null, inverted);
|
||||
}
|
||||
|
||||
static HeaderMatcher forPresent(String name, boolean present, boolean inverted) {
|
||||
checkNotNull(name, "name");
|
||||
return HeaderMatcher.create(name, null, null, null, present, null, null, inverted);
|
||||
}
|
||||
|
||||
static HeaderMatcher forPrefix(String name, String prefix, boolean inverted) {
|
||||
checkNotNull(name, "name");
|
||||
checkNotNull(prefix, "prefix");
|
||||
return HeaderMatcher.create(name, null, null, null, null, prefix, null, inverted);
|
||||
}
|
||||
|
||||
static HeaderMatcher forSuffix(String name, String suffix, boolean inverted) {
|
||||
checkNotNull(name, "name");
|
||||
checkNotNull(suffix, "suffix");
|
||||
return HeaderMatcher.create(name, null, null, null, null, null, suffix, inverted);
|
||||
}
|
||||
|
||||
private static HeaderMatcher create(String name, @Nullable String exactValue,
|
||||
@Nullable Pattern safeRegEx, @Nullable Range range,
|
||||
@Nullable Boolean present, @Nullable String prefix,
|
||||
@Nullable String suffix, boolean inverted) {
|
||||
checkNotNull(name, "name");
|
||||
return new AutoValue_Matchers_HeaderMatcher(name, exactValue, safeRegEx, range, present,
|
||||
prefix, suffix, inverted);
|
||||
}
|
||||
|
||||
/** Represents an integer range. */
|
||||
@AutoValue
|
||||
abstract static class Range {
|
||||
abstract long start();
|
||||
|
||||
abstract long end();
|
||||
|
||||
static Range create(long start, long end) {
|
||||
return new AutoValue_Matchers_HeaderMatcher_Range(start, end);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/** Represents a fractional value. */
|
||||
@AutoValue
|
||||
abstract static class FractionMatcher {
|
||||
abstract int numerator();
|
||||
|
||||
abstract int denominator();
|
||||
|
||||
static FractionMatcher create(int numerator, int denominator) {
|
||||
return new AutoValue_Matchers_FractionMatcher(numerator, denominator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -25,9 +25,8 @@ import com.google.common.collect.ImmutableList;
|
|||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.re2j.Pattern;
|
||||
import io.grpc.xds.Filter.FilterConfig;
|
||||
import io.grpc.xds.Matchers.FractionMatcher;
|
||||
import io.grpc.xds.Matchers.HeaderMatcher;
|
||||
import io.grpc.xds.Matchers.PathMatcher;
|
||||
import io.grpc.xds.internal.Matchers.FractionMatcher;
|
||||
import io.grpc.xds.internal.Matchers.HeaderMatcher;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
|
@ -90,6 +89,47 @@ abstract class VirtualHost {
|
|||
return new AutoValue_VirtualHost_Route_RouteMatch(pathMatcher,
|
||||
ImmutableList.copyOf(headerMatchers), fractionMatcher);
|
||||
}
|
||||
|
||||
/** Matcher for HTTP request path. */
|
||||
@AutoValue
|
||||
abstract static class PathMatcher {
|
||||
// Exact full path to be matched.
|
||||
@Nullable
|
||||
abstract String path();
|
||||
|
||||
// Path prefix to be matched.
|
||||
@Nullable
|
||||
abstract String prefix();
|
||||
|
||||
// Regular expression pattern of the path to be matched.
|
||||
@Nullable
|
||||
abstract Pattern regEx();
|
||||
|
||||
// Whether case sensitivity is taken into account for matching.
|
||||
// Only valid for full path matching or prefix matching.
|
||||
abstract boolean caseSensitive();
|
||||
|
||||
static PathMatcher fromPath(String path, boolean caseSensitive) {
|
||||
checkNotNull(path, "path");
|
||||
return create(path, null, null, caseSensitive);
|
||||
}
|
||||
|
||||
static PathMatcher fromPrefix(String prefix, boolean caseSensitive) {
|
||||
checkNotNull(prefix, "prefix");
|
||||
return create(null, prefix, null, caseSensitive);
|
||||
}
|
||||
|
||||
static PathMatcher fromRegEx(Pattern regEx) {
|
||||
checkNotNull(regEx, "regEx");
|
||||
return create(null, null, regEx, false /* doesn't matter */);
|
||||
}
|
||||
|
||||
private static PathMatcher create(@Nullable String path, @Nullable String prefix,
|
||||
@Nullable Pattern regEx, boolean caseSensitive) {
|
||||
return new AutoValue_VirtualHost_Route_RouteMatch_PathMatcher(path, prefix, regEx,
|
||||
caseSensitive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
|
|
|
|||
|
|
@ -30,12 +30,12 @@ import io.grpc.internal.SharedResourceHolder;
|
|||
import io.grpc.xds.EnvoyServerProtoData.CidrRange;
|
||||
import io.grpc.xds.EnvoyServerProtoData.FilterChain;
|
||||
import io.grpc.xds.EnvoyServerProtoData.FilterChainMatch;
|
||||
import io.grpc.xds.internal.Matchers.CidrMatcher;
|
||||
import io.grpc.xds.internal.sds.SslContextProviderSupplier;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.epoll.Epoll;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.netty.util.concurrent.DefaultThreadFactory;
|
||||
import java.math.BigInteger;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
|
|
@ -338,20 +338,8 @@ public final class XdsClientWrapperForServerSds {
|
|||
return filtered;
|
||||
}
|
||||
|
||||
private static boolean isCidrMatching(byte[] cidrBytes, byte[] addressBytes, int prefixLen) {
|
||||
BigInteger cidrInt = new BigInteger(cidrBytes);
|
||||
BigInteger addrInt = new BigInteger(addressBytes);
|
||||
|
||||
int shiftAmount = 8 * cidrBytes.length - prefixLen;
|
||||
|
||||
cidrInt = cidrInt.shiftRight(shiftAmount);
|
||||
addrInt = addrInt.shiftRight(shiftAmount);
|
||||
return cidrInt.equals(addrInt);
|
||||
}
|
||||
|
||||
private static int getMatchingPrefixLength(
|
||||
FilterChainMatch filterChainMatch, InetAddress address, boolean forDestination) {
|
||||
byte[] addressBytes = address.getAddress();
|
||||
boolean isIPv6 = address instanceof Inet6Address;
|
||||
List<CidrRange> cidrRanges =
|
||||
forDestination
|
||||
|
|
@ -366,10 +354,9 @@ public final class XdsClientWrapperForServerSds {
|
|||
InetAddress cidrAddr = cidrRange.getAddressPrefix();
|
||||
boolean cidrIsIpv6 = cidrAddr instanceof Inet6Address;
|
||||
if (isIPv6 == cidrIsIpv6) {
|
||||
byte[] cidrBytes = cidrAddr.getAddress();
|
||||
int prefixLen = cidrRange.getPrefixLen();
|
||||
if (isCidrMatching(cidrBytes, addressBytes, prefixLen)
|
||||
&& prefixLen > matchingPrefixLength) {
|
||||
CidrMatcher matcher = CidrMatcher.create(cidrAddr, prefixLen);
|
||||
if (matcher.matches(address) && prefixLen > matchingPrefixLength) {
|
||||
matchingPrefixLength = prefixLen;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,15 +46,13 @@ import io.grpc.internal.ObjectPool;
|
|||
import io.grpc.xds.Filter.ClientInterceptorBuilder;
|
||||
import io.grpc.xds.Filter.FilterConfig;
|
||||
import io.grpc.xds.Filter.NamedFilterConfig;
|
||||
import io.grpc.xds.Matchers.FractionMatcher;
|
||||
import io.grpc.xds.Matchers.HeaderMatcher;
|
||||
import io.grpc.xds.Matchers.PathMatcher;
|
||||
import io.grpc.xds.ThreadSafeRandom.ThreadSafeRandomImpl;
|
||||
import io.grpc.xds.VirtualHost.Route;
|
||||
import io.grpc.xds.VirtualHost.Route.RouteAction;
|
||||
import io.grpc.xds.VirtualHost.Route.RouteAction.ClusterWeight;
|
||||
import io.grpc.xds.VirtualHost.Route.RouteAction.HashPolicy;
|
||||
import io.grpc.xds.VirtualHost.Route.RouteMatch;
|
||||
import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher;
|
||||
import io.grpc.xds.XdsClient.LdsResourceWatcher;
|
||||
import io.grpc.xds.XdsClient.LdsUpdate;
|
||||
import io.grpc.xds.XdsClient.RdsResourceWatcher;
|
||||
|
|
@ -62,6 +60,8 @@ import io.grpc.xds.XdsClient.RdsUpdate;
|
|||
import io.grpc.xds.XdsLogger.XdsLogLevel;
|
||||
import io.grpc.xds.XdsNameResolverProvider.CallCounterProvider;
|
||||
import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory;
|
||||
import io.grpc.xds.internal.Matchers.FractionMatcher;
|
||||
import io.grpc.xds.internal.Matchers.HeaderMatcher;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
|
@ -577,6 +577,7 @@ final class XdsNameResolver extends NameResolver {
|
|||
return pathMatcher.regEx().matches(fullMethodName);
|
||||
}
|
||||
|
||||
// TODO(zivy): consider reuse Matchers.HeaderMatcher.matches()
|
||||
private static boolean matchHeader(HeaderMatcher headerMatcher, @Nullable String value) {
|
||||
if (headerMatcher.present() != null) {
|
||||
return (value == null) == headerMatcher.present().equals(headerMatcher.inverted());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
* Copyright 2021 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.grpc.xds.internal;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.re2j.Pattern;
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Provides a group of request matchers. A matcher evaluates an input and tells whether certain
|
||||
* argument in the input matches a predefined matching pattern.
|
||||
*/
|
||||
public final class Matchers {
|
||||
|
||||
/** Matcher for HTTP request headers. */
|
||||
@AutoValue
|
||||
public abstract static class HeaderMatcher {
|
||||
// Name of the header to be matched.
|
||||
public abstract String name();
|
||||
|
||||
// Matches exact header value.
|
||||
@Nullable
|
||||
public abstract String exactValue();
|
||||
|
||||
// Matches header value with the regular expression pattern.
|
||||
@Nullable
|
||||
public abstract Pattern safeRegEx();
|
||||
|
||||
// Matches header value an integer value in the range.
|
||||
@Nullable
|
||||
public abstract Range range();
|
||||
|
||||
// Matches header presence.
|
||||
@Nullable
|
||||
public abstract Boolean present();
|
||||
|
||||
// Matches header value with the prefix.
|
||||
@Nullable
|
||||
public abstract String prefix();
|
||||
|
||||
// Matches header value with the suffix.
|
||||
@Nullable
|
||||
public abstract String suffix();
|
||||
|
||||
// Whether the matching semantics is inverted. E.g., present && !inverted -> !present
|
||||
public abstract boolean inverted();
|
||||
|
||||
/** The request header value should exactly match the specified value. */
|
||||
public static HeaderMatcher forExactValue(String name, String exactValue, boolean inverted) {
|
||||
checkNotNull(name, "name");
|
||||
checkNotNull(exactValue, "exactValue");
|
||||
return HeaderMatcher.create(name, exactValue, null, null, null, null, null, inverted);
|
||||
}
|
||||
|
||||
/** The request header value should match the regular expression pattern. */
|
||||
public static HeaderMatcher forSafeRegEx(String name, Pattern safeRegEx, boolean inverted) {
|
||||
checkNotNull(name, "name");
|
||||
checkNotNull(safeRegEx, "safeRegEx");
|
||||
return HeaderMatcher.create(name, null, safeRegEx, null, null, null, null, inverted);
|
||||
}
|
||||
|
||||
/** The request header value should be within the range. */
|
||||
public static HeaderMatcher forRange(String name, Range range, boolean inverted) {
|
||||
checkNotNull(name, "name");
|
||||
checkNotNull(range, "range");
|
||||
return HeaderMatcher.create(name, null, null, range, null, null, null, inverted);
|
||||
}
|
||||
|
||||
/** The request header value should exist. */
|
||||
public static HeaderMatcher forPresent(String name, boolean present, boolean inverted) {
|
||||
checkNotNull(name, "name");
|
||||
return HeaderMatcher.create(name, null, null, null, present, null, null, inverted);
|
||||
}
|
||||
|
||||
/** The request header value should have this prefix. */
|
||||
public static HeaderMatcher forPrefix(String name, String prefix, boolean inverted) {
|
||||
checkNotNull(name, "name");
|
||||
checkNotNull(prefix, "prefix");
|
||||
return HeaderMatcher.create(name, null, null, null, null, prefix, null, inverted);
|
||||
}
|
||||
|
||||
/** The request header value should have this suffix. */
|
||||
public static HeaderMatcher forSuffix(String name, String suffix, boolean inverted) {
|
||||
checkNotNull(name, "name");
|
||||
checkNotNull(suffix, "suffix");
|
||||
return HeaderMatcher.create(name, null, null, null, null, null, suffix, inverted);
|
||||
}
|
||||
|
||||
private static HeaderMatcher create(String name, @Nullable String exactValue,
|
||||
@Nullable Pattern safeRegEx, @Nullable Range range,
|
||||
@Nullable Boolean present, @Nullable String prefix,
|
||||
@Nullable String suffix, boolean inverted) {
|
||||
checkNotNull(name, "name");
|
||||
return new AutoValue_Matchers_HeaderMatcher(name, exactValue, safeRegEx, range, present,
|
||||
prefix, suffix, inverted);
|
||||
}
|
||||
|
||||
/** Returns the matching result. */
|
||||
public boolean matches(@Nullable String value) {
|
||||
if (present() != null) {
|
||||
return (value == null) == present().equals(inverted());
|
||||
}
|
||||
// FIXME(zivy@): invert result for null value.
|
||||
// https://github.com/envoyproxy/envoy/blob/0fae6970ddaf93f024908ba304bbd2b34e997a51/source/common/http/header_utility.cc#L130
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
boolean baseMatch;
|
||||
if (exactValue() != null) {
|
||||
baseMatch = exactValue().equals(value);
|
||||
} else if (safeRegEx() != null) {
|
||||
baseMatch = safeRegEx().matches(value);
|
||||
} else if (range() != null) {
|
||||
long numValue;
|
||||
try {
|
||||
numValue = Long.parseLong(value);
|
||||
baseMatch = numValue >= range().start()
|
||||
&& numValue <= range().end();
|
||||
} catch (NumberFormatException ignored) {
|
||||
baseMatch = false;
|
||||
}
|
||||
} else if (prefix() != null) {
|
||||
baseMatch = value.startsWith(prefix());
|
||||
} else {
|
||||
baseMatch = value.endsWith(suffix());
|
||||
}
|
||||
return baseMatch != inverted();
|
||||
}
|
||||
|
||||
/** Represents an integer range. */
|
||||
@AutoValue
|
||||
public abstract static class Range {
|
||||
public abstract long start();
|
||||
|
||||
public abstract long end();
|
||||
|
||||
public static Range create(long start, long end) {
|
||||
return new AutoValue_Matchers_HeaderMatcher_Range(start, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Represents a fractional value. */
|
||||
@AutoValue
|
||||
public abstract static class FractionMatcher {
|
||||
public abstract int numerator();
|
||||
|
||||
public abstract int denominator();
|
||||
|
||||
public static FractionMatcher create(int numerator, int denominator) {
|
||||
return new AutoValue_Matchers_FractionMatcher(numerator, denominator);
|
||||
}
|
||||
}
|
||||
|
||||
/** Represents various ways to match a string .*/
|
||||
@AutoValue
|
||||
public abstract static class StringMatcher {
|
||||
@Nullable
|
||||
abstract String exact();
|
||||
|
||||
// The input string has this prefix.
|
||||
@Nullable
|
||||
abstract String prefix();
|
||||
|
||||
// The input string has this suffix.
|
||||
@Nullable
|
||||
abstract String suffix();
|
||||
|
||||
// The input string matches the regular expression.
|
||||
@Nullable
|
||||
abstract Pattern regEx();
|
||||
|
||||
// The input string has this substring.
|
||||
@Nullable
|
||||
abstract String contains();
|
||||
|
||||
// If true, exact/prefix/suffix matching should be case insensitive.
|
||||
abstract boolean ignoreCase();
|
||||
|
||||
/** The input string should exactly matches the specified string. */
|
||||
public static StringMatcher forExact(String exact, boolean ignoreCase) {
|
||||
checkNotNull(exact, "exact");
|
||||
return StringMatcher.create(exact, null, null, null, null,
|
||||
ignoreCase);
|
||||
}
|
||||
|
||||
/** The input string should have the prefix. */
|
||||
public static StringMatcher forPrefix(String prefix, boolean ignoreCase) {
|
||||
checkNotNull(prefix, "prefix");
|
||||
return StringMatcher.create(null, prefix, null, null, null,
|
||||
ignoreCase);
|
||||
}
|
||||
|
||||
/** The input string should have the suffix. */
|
||||
public static StringMatcher forSuffix(String suffix, boolean ignoreCase) {
|
||||
checkNotNull(suffix, "suffix");
|
||||
return StringMatcher.create(null, null, suffix, null, null,
|
||||
ignoreCase);
|
||||
}
|
||||
|
||||
/** The input string should match this pattern. */
|
||||
public static StringMatcher forSafeRegEx(Pattern regEx) {
|
||||
checkNotNull(regEx, "regEx");
|
||||
return StringMatcher.create(null, null, null, regEx, null,
|
||||
false/* doesn't matter */);
|
||||
}
|
||||
|
||||
/** The input string should contain this substring. */
|
||||
public static StringMatcher forContains(String contains) {
|
||||
checkNotNull(contains, "contains");
|
||||
return StringMatcher.create(null, null, null, null, contains,
|
||||
false/* doesn't matter */);
|
||||
}
|
||||
|
||||
/** Returns the matching result for this string. */
|
||||
public boolean matches(String args) {
|
||||
if (args == null) {
|
||||
return false;
|
||||
}
|
||||
if (exact() != null) {
|
||||
return ignoreCase()
|
||||
? exact().equalsIgnoreCase(args)
|
||||
: exact().equals(args);
|
||||
} else if (prefix() != null) {
|
||||
return ignoreCase()
|
||||
? args.toLowerCase().startsWith(prefix().toLowerCase())
|
||||
: args.startsWith(prefix());
|
||||
} else if (suffix() != null) {
|
||||
return ignoreCase()
|
||||
? args.toLowerCase().endsWith(suffix().toLowerCase())
|
||||
: args.endsWith(suffix());
|
||||
} else if (contains() != null) {
|
||||
return args.contains(contains());
|
||||
}
|
||||
return regEx().matches(args);
|
||||
}
|
||||
|
||||
private static StringMatcher create(@Nullable String exact, @Nullable String prefix,
|
||||
@Nullable String suffix, @Nullable Pattern regEx, @Nullable String contains,
|
||||
boolean ignoreCase) {
|
||||
return new AutoValue_Matchers_StringMatcher(exact, prefix, suffix, regEx, contains,
|
||||
ignoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
/** Matcher to evaluate whether an IPv4 or IPv6 address is within a CIDR range. */
|
||||
@AutoValue
|
||||
public abstract static class CidrMatcher {
|
||||
|
||||
abstract InetAddress addressPrefix();
|
||||
|
||||
abstract int prefixLen();
|
||||
|
||||
/** Returns matching result for this address. */
|
||||
public boolean matches(InetAddress address) {
|
||||
if (address == null) {
|
||||
return false;
|
||||
}
|
||||
byte[] cidr = addressPrefix().getAddress();
|
||||
byte[] addr = address.getAddress();
|
||||
if (addr.length != cidr.length) {
|
||||
return false;
|
||||
}
|
||||
BigInteger cidrInt = new BigInteger(cidr);
|
||||
BigInteger addrInt = new BigInteger(addr);
|
||||
|
||||
int shiftAmount = 8 * cidr.length - prefixLen();
|
||||
|
||||
cidrInt = cidrInt.shiftRight(shiftAmount);
|
||||
addrInt = addrInt.shiftRight(shiftAmount);
|
||||
return cidrInt.equals(addrInt);
|
||||
}
|
||||
|
||||
/** Constructs a CidrMatcher with this prefix and prefix length.
|
||||
* Do not provide string addressPrefix constructor to avoid IO exception handling.
|
||||
* */
|
||||
public static CidrMatcher create(InetAddress addressPrefix, int prefixLen) {
|
||||
return new AutoValue_Matchers_CidrMatcher(addressPrefix, prefixLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,415 @@
|
|||
/*
|
||||
* Copyright 2021 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.grpc.xds.internal.rbac.engine;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.base.Joiner;
|
||||
import io.grpc.Grpc;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.ServerCall;
|
||||
import io.grpc.xds.internal.Matchers;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import javax.net.ssl.SSLSession;
|
||||
|
||||
/**
|
||||
* Implementation of gRPC server access control based on envoy RBAC protocol:
|
||||
* https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/rbac/v3/rbac.proto
|
||||
*
|
||||
* <p>One GrpcAuthorizationEngine is initialized with one action type and a list of policies.
|
||||
* Policies are examined sequentially in order in an any match fashion, and the first matched policy
|
||||
* will be returned. If not matched at all, the opposite action type is returned as a result.
|
||||
*/
|
||||
public final class GrpcAuthorizationEngine {
|
||||
private static final Logger log = Logger.getLogger(GrpcAuthorizationEngine.class.getName());
|
||||
|
||||
private final AuthConfig authConfig;
|
||||
|
||||
/** Instantiated with envoy policyMatcher configuration. */
|
||||
public GrpcAuthorizationEngine(AuthConfig authConfig) {
|
||||
this.authConfig = authConfig;
|
||||
}
|
||||
|
||||
/** Return the auth decision for the request argument against the policies. */
|
||||
public AuthDecision evaluate(Metadata metadata, ServerCall<?,?> serverCall) {
|
||||
checkNotNull(metadata, "metadata");
|
||||
checkNotNull(serverCall, "serverCall");
|
||||
String firstMatch = null;
|
||||
EvaluateArgs args = new EvaluateArgs(metadata, serverCall);
|
||||
for (PolicyMatcher policyMatcher : authConfig.policies) {
|
||||
if (policyMatcher.matches(args)) {
|
||||
firstMatch = policyMatcher.name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Action decisionType = Action.DENY;
|
||||
if (Action.DENY.equals(authConfig.action) == (firstMatch == null)) {
|
||||
decisionType = Action.ALLOW;
|
||||
}
|
||||
log.log(Level.FINER, "RBAC decision: {0}, policy match: {1}.",
|
||||
new Object[]{decisionType, firstMatch});
|
||||
return AuthDecision.create(decisionType, firstMatch);
|
||||
}
|
||||
|
||||
public enum Action {
|
||||
ALLOW,
|
||||
DENY,
|
||||
}
|
||||
|
||||
/**
|
||||
* An authorization decision provides information about the decision type and the policy name
|
||||
* identifier based on the authorization engine evaluation. */
|
||||
@AutoValue
|
||||
public abstract static class AuthDecision {
|
||||
public abstract Action decision();
|
||||
|
||||
@Nullable
|
||||
public abstract String matchingPolicyName();
|
||||
|
||||
static AuthDecision create(Action decisionType, @Nullable String matchingPolicy) {
|
||||
return new AutoValue_GrpcAuthorizationEngine_AuthDecision(decisionType, matchingPolicy);
|
||||
}
|
||||
}
|
||||
|
||||
/** Represents authorization config policy that the engine will evaluate against. */
|
||||
public static final class AuthConfig {
|
||||
private final List<PolicyMatcher> policies;
|
||||
private final Action action;
|
||||
|
||||
public AuthConfig(List<PolicyMatcher> policies, Action action) {
|
||||
this.policies = Collections.unmodifiableList(new ArrayList<>(policies));
|
||||
this.action = action;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a top level {@link Matcher} for a single RBAC policy configuration per envoy
|
||||
* protocol:
|
||||
* https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/rbac/v3/rbac.proto#config-rbac-v3-policy.
|
||||
*
|
||||
* <p>Currently we only support matching some of the request fields. Those unsupported fields are
|
||||
* considered not match until we stop ignoring them.
|
||||
*/
|
||||
public static final class PolicyMatcher implements Matcher {
|
||||
private final OrMatcher permissions;
|
||||
private final OrMatcher principals;
|
||||
private final String name;
|
||||
|
||||
/** Constructs a matcher for one RBAC policy. */
|
||||
public PolicyMatcher(String name, OrMatcher permissions, OrMatcher principals) {
|
||||
this.name = name;
|
||||
this.permissions = permissions;
|
||||
this.principals = principals;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(EvaluateArgs args) {
|
||||
return permissions.matches(args) && principals.matches(args);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class AuthenticatedMatcher implements Matcher {
|
||||
private final Matchers.StringMatcher delegate;
|
||||
|
||||
/**
|
||||
* Passing in null will match all authenticated user, i.e. SSL session is present.
|
||||
* https://github.com/envoyproxy/envoy/blob/main/api/envoy/config/rbac/v3/rbac.proto#L240
|
||||
* */
|
||||
public AuthenticatedMatcher(@Nullable Matchers.StringMatcher delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(EvaluateArgs args) {
|
||||
Collection<String> principalNames = args.getPrincipalNames();
|
||||
log.log(Level.FINER, "Matching principal names: {0}", new Object[]{principalNames});
|
||||
// Null means unauthenticated connection.
|
||||
if (principalNames == null) {
|
||||
return false;
|
||||
}
|
||||
// Connection is authenticated, so returns match when delegated string matcher is not present.
|
||||
if (delegate == null) {
|
||||
return true;
|
||||
}
|
||||
for (String name : principalNames) {
|
||||
if (delegate.matches(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class DestinationIpMatcher implements Matcher {
|
||||
private final Matchers.CidrMatcher delegate;
|
||||
|
||||
public DestinationIpMatcher(Matchers.CidrMatcher delegate) {
|
||||
this.delegate = checkNotNull(delegate, "delegate");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(EvaluateArgs args) {
|
||||
return delegate.matches(args.getDestinationIp());
|
||||
}
|
||||
}
|
||||
|
||||
public static final class SourceIpMatcher implements Matcher {
|
||||
private final Matchers.CidrMatcher delegate;
|
||||
|
||||
public SourceIpMatcher(Matchers.CidrMatcher delegate) {
|
||||
this.delegate = checkNotNull(delegate, "delegate");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(EvaluateArgs args) {
|
||||
return delegate.matches(args.getSourceIp());
|
||||
}
|
||||
}
|
||||
|
||||
public static final class PathMatcher implements Matcher {
|
||||
private final Matchers.StringMatcher delegate;
|
||||
|
||||
public PathMatcher(Matchers.StringMatcher delegate) {
|
||||
this.delegate = checkNotNull(delegate, "delegate");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(EvaluateArgs args) {
|
||||
return delegate.matches(args.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
public static final class HeaderMatcher implements Matcher {
|
||||
private final Matchers.HeaderMatcher delegate;
|
||||
|
||||
public HeaderMatcher(Matchers.HeaderMatcher delegate) {
|
||||
this.delegate = checkNotNull(delegate, "delegate");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(EvaluateArgs args) {
|
||||
return delegate.matches(args.getHeader(delegate.name()));
|
||||
}
|
||||
}
|
||||
|
||||
public static final class DestinationPortMatcher implements Matcher {
|
||||
private final int port;
|
||||
|
||||
public DestinationPortMatcher(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(EvaluateArgs args) {
|
||||
return port == args.getDestinationPort();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class EvaluateArgs {
|
||||
private final Metadata metadata;
|
||||
private final ServerCall<?,?> serverCall;
|
||||
// https://github.com/envoyproxy/envoy/blob/63619d578e1abe0c1725ea28ba02f361466662e1/api/envoy/config/rbac/v3/rbac.proto#L238-L240
|
||||
private static final int URI_SAN = 6;
|
||||
private static final int DNS_SAN = 2;
|
||||
|
||||
private EvaluateArgs(Metadata metadata, ServerCall<?,?> serverCall) {
|
||||
this.metadata = metadata;
|
||||
this.serverCall = serverCall;
|
||||
}
|
||||
|
||||
private String getPath() {
|
||||
return "/" + serverCall.getMethodDescriptor().getFullMethodName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null for unauthenticated connection.
|
||||
* Returns empty string collection if no valid certificate and no
|
||||
* principal names we are interested in.
|
||||
* https://github.com/envoyproxy/envoy/blob/0fae6970ddaf93f024908ba304bbd2b34e997a51/envoy/ssl/connection.h#L70
|
||||
*/
|
||||
private Collection<String> getPrincipalNames() {
|
||||
SSLSession sslSession = serverCall.getAttributes().get(Grpc.TRANSPORT_ATTR_SSL_SESSION);
|
||||
if (sslSession == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Certificate[] certs = sslSession.getPeerCertificates();
|
||||
if (certs == null || certs.length < 1) {
|
||||
return Collections.singleton("");
|
||||
}
|
||||
X509Certificate cert = (X509Certificate)certs[0];
|
||||
if (cert == null) {
|
||||
return Collections.singleton("");
|
||||
}
|
||||
Collection<List<?>> names = cert.getSubjectAlternativeNames();
|
||||
List<String> principalNames = new ArrayList<>();
|
||||
if (names != null) {
|
||||
for (List<?> name : names) {
|
||||
if (URI_SAN == (Integer) name.get(0)) {
|
||||
principalNames.add((String) name.get(1));
|
||||
}
|
||||
}
|
||||
if (!principalNames.isEmpty()) {
|
||||
return Collections.unmodifiableCollection(principalNames);
|
||||
}
|
||||
for (List<?> name : names) {
|
||||
if (DNS_SAN == (Integer) name.get(0)) {
|
||||
principalNames.add((String) name.get(1));
|
||||
}
|
||||
}
|
||||
if (!principalNames.isEmpty()) {
|
||||
return Collections.unmodifiableCollection(principalNames);
|
||||
}
|
||||
}
|
||||
if (cert.getSubjectDN() == null || cert.getSubjectDN().getName() == null) {
|
||||
return Collections.singleton("");
|
||||
}
|
||||
return Collections.singleton(cert.getSubjectDN().getName());
|
||||
} catch (SSLPeerUnverifiedException | CertificateParsingException ex) {
|
||||
log.log(Level.FINE, "Unexpected getPrincipalNames error.", ex);
|
||||
return Collections.singleton("");
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getHeader(String headerName) {
|
||||
if (headerName.endsWith(Metadata.BINARY_HEADER_SUFFIX)) {
|
||||
return null;
|
||||
}
|
||||
Metadata.Key<String> key;
|
||||
try {
|
||||
key = Metadata.Key.of(headerName, Metadata.ASCII_STRING_MARSHALLER);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
Iterable<String> values = metadata.getAll(key);
|
||||
return values == null ? null : Joiner.on(",").join(values);
|
||||
}
|
||||
|
||||
private InetAddress getDestinationIp() {
|
||||
SocketAddress addr = serverCall.getAttributes().get(Grpc.TRANSPORT_ATTR_LOCAL_ADDR);
|
||||
return addr == null ? null : ((InetSocketAddress) addr).getAddress();
|
||||
}
|
||||
|
||||
private InetAddress getSourceIp() {
|
||||
SocketAddress addr = serverCall.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR);
|
||||
return addr == null ? null : ((InetSocketAddress) addr).getAddress();
|
||||
}
|
||||
|
||||
private int getDestinationPort() {
|
||||
SocketAddress addr = serverCall.getAttributes().get(Grpc.TRANSPORT_ATTR_LOCAL_ADDR);
|
||||
return addr == null ? -1 : ((InetSocketAddress) addr).getPort();
|
||||
}
|
||||
}
|
||||
|
||||
interface Matcher {
|
||||
boolean matches(EvaluateArgs args);
|
||||
}
|
||||
|
||||
public static final class OrMatcher implements Matcher {
|
||||
private final List<? extends Matcher> anyMatch;
|
||||
|
||||
/** Matches when any of the matcher matches. */
|
||||
public OrMatcher(List<? extends Matcher> matchers) {
|
||||
checkNotNull(matchers, "matchers");
|
||||
for (Matcher matcher : matchers) {
|
||||
checkNotNull(matcher, "matcher");
|
||||
}
|
||||
this.anyMatch = Collections.unmodifiableList(new ArrayList<>(matchers));
|
||||
}
|
||||
|
||||
public static OrMatcher create(Matcher...matchers) {
|
||||
return new OrMatcher(Arrays.asList(matchers));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(EvaluateArgs args) {
|
||||
for (Matcher m : anyMatch) {
|
||||
if (m.matches(args)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class AndMatcher implements Matcher {
|
||||
private final List<? extends Matcher> allMatch;
|
||||
|
||||
/** Matches when all of the matchers match. */
|
||||
public AndMatcher(List<? extends Matcher> matchers) {
|
||||
checkNotNull(matchers, "matchers");
|
||||
for (Matcher matcher : matchers) {
|
||||
checkNotNull(matcher, "matcher");
|
||||
}
|
||||
this.allMatch = Collections.unmodifiableList(new ArrayList<>(matchers));
|
||||
}
|
||||
|
||||
public static AndMatcher create(Matcher...matchers) {
|
||||
return new AndMatcher(Arrays.asList(matchers));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(EvaluateArgs args) {
|
||||
for (Matcher m : allMatch) {
|
||||
if (!m.matches(args)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/** Always true matcher.*/
|
||||
public static final class AlwaysTrueMatcher implements Matcher {
|
||||
static AlwaysTrueMatcher INSTANCE = new AlwaysTrueMatcher();
|
||||
|
||||
@Override
|
||||
public boolean matches(EvaluateArgs args) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/** Negate matcher.*/
|
||||
public static final class InvertMatcher implements Matcher {
|
||||
private final Matcher toInvertMatcher;
|
||||
|
||||
public InvertMatcher(Matcher matcher) {
|
||||
this.toInvertMatcher = checkNotNull(matcher, "matcher");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(EvaluateArgs args) {
|
||||
return !toInvertMatcher.matches(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -73,15 +73,15 @@ import io.grpc.xds.Endpoints.LbEndpoint;
|
|||
import io.grpc.xds.Endpoints.LocalityLbEndpoints;
|
||||
import io.grpc.xds.FaultConfig.FaultAbort;
|
||||
import io.grpc.xds.Filter.FilterConfig;
|
||||
import io.grpc.xds.Matchers.FractionMatcher;
|
||||
import io.grpc.xds.Matchers.HeaderMatcher;
|
||||
import io.grpc.xds.Matchers.PathMatcher;
|
||||
import io.grpc.xds.VirtualHost.Route;
|
||||
import io.grpc.xds.VirtualHost.Route.RouteAction;
|
||||
import io.grpc.xds.VirtualHost.Route.RouteAction.ClusterWeight;
|
||||
import io.grpc.xds.VirtualHost.Route.RouteAction.HashPolicy;
|
||||
import io.grpc.xds.VirtualHost.Route.RouteMatch;
|
||||
import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher;
|
||||
import io.grpc.xds.XdsClient.CdsUpdate;
|
||||
import io.grpc.xds.internal.Matchers.FractionMatcher;
|
||||
import io.grpc.xds.internal.Matchers.HeaderMatcher;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
|
|
|
|||
|
|
@ -67,14 +67,14 @@ import io.grpc.xds.FaultConfig.FaultAbort;
|
|||
import io.grpc.xds.FaultConfig.FaultDelay;
|
||||
import io.grpc.xds.Filter.FilterConfig;
|
||||
import io.grpc.xds.Filter.NamedFilterConfig;
|
||||
import io.grpc.xds.Matchers.HeaderMatcher;
|
||||
import io.grpc.xds.Matchers.PathMatcher;
|
||||
import io.grpc.xds.VirtualHost.Route;
|
||||
import io.grpc.xds.VirtualHost.Route.RouteAction;
|
||||
import io.grpc.xds.VirtualHost.Route.RouteAction.ClusterWeight;
|
||||
import io.grpc.xds.VirtualHost.Route.RouteAction.HashPolicy;
|
||||
import io.grpc.xds.VirtualHost.Route.RouteMatch;
|
||||
import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher;
|
||||
import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory;
|
||||
import io.grpc.xds.internal.Matchers.HeaderMatcher;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* Copyright 2021 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.grpc.xds.internal;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import com.google.re2j.Pattern;
|
||||
import io.grpc.xds.internal.Matchers.CidrMatcher;
|
||||
import io.grpc.xds.internal.Matchers.HeaderMatcher;
|
||||
import io.grpc.xds.internal.Matchers.HeaderMatcher.Range;
|
||||
import io.grpc.xds.internal.Matchers.StringMatcher;
|
||||
import java.net.InetAddress;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class MatcherTest {
|
||||
@Rule
|
||||
public final MockitoRule mocks = MockitoJUnit.rule();
|
||||
|
||||
@Test
|
||||
public void ipMatcher_Ipv4() throws Exception {
|
||||
CidrMatcher matcher = CidrMatcher.create(InetAddress.getByName("10.10.24.10"), 20);
|
||||
assertThat(matcher.matches(InetAddress.getByName("::0"))).isFalse();
|
||||
assertThat(matcher.matches(InetAddress.getByName("10.10.20.0"))).isTrue();
|
||||
assertThat(matcher.matches(InetAddress.getByName("10.10.16.0"))).isTrue();
|
||||
assertThat(matcher.matches(InetAddress.getByName("10.10.24.10"))).isTrue();
|
||||
assertThat(matcher.matches(InetAddress.getByName("10.10.31.0"))).isTrue();
|
||||
assertThat(matcher.matches(InetAddress.getByName("10.10.17.0"))).isTrue();
|
||||
assertThat(matcher.matches(InetAddress.getByName("10.32.20.0"))).isFalse();
|
||||
assertThat(matcher.matches(InetAddress.getByName("10.10.40.0"))).isFalse();
|
||||
matcher = CidrMatcher.create(InetAddress.getByName("0.0.0.0"), 20);
|
||||
assertThat(matcher.matches(InetAddress.getByName("10.32.20.0"))).isFalse();
|
||||
assertThat(matcher.matches(InetAddress.getByName("0.0.31.0"))).isFalse();
|
||||
assertThat(matcher.matches(InetAddress.getByName("0.0.15.0"))).isTrue();
|
||||
assertThat(matcher.matches(null)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ipMatcher_Ipv6() throws Exception {
|
||||
CidrMatcher matcher = CidrMatcher.create(InetAddress.getByName("2012:00fe:d808::"), 36);
|
||||
assertThat(matcher.matches(InetAddress.getByName("0.0.0.0"))).isFalse();
|
||||
assertThat(matcher.matches(InetAddress.getByName("2012:00fe:d000::0"))).isTrue();
|
||||
assertThat(matcher.matches(InetAddress.getByName("2012:00fe:d808::"))).isTrue();
|
||||
assertThat(matcher.matches(InetAddress.getByName("2012:00fe:da81:0909:0008:4018:e930:b019")))
|
||||
.isTrue();
|
||||
assertThat(matcher.matches(InetAddress.getByName("2013:00fe:d000::0"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stringMatcher() {
|
||||
StringMatcher matcher = StringMatcher.forExact("essence", false);
|
||||
assertThat(matcher.matches("elite")).isFalse();
|
||||
assertThat(matcher.matches("ess")).isFalse();
|
||||
assertThat(matcher.matches("")).isFalse();
|
||||
assertThat(matcher.matches("essential")).isFalse();
|
||||
assertThat(matcher.matches("Essence")).isFalse();
|
||||
assertThat(matcher.matches("essence")).isTrue();
|
||||
assertThat(matcher.matches((String)null)).isFalse();
|
||||
matcher = StringMatcher.forExact("essence", true);
|
||||
assertThat(matcher.matches("Essence")).isTrue();
|
||||
assertThat(matcher.matches("essence")).isTrue();
|
||||
matcher = StringMatcher.forExact("", true);
|
||||
assertThat(matcher.matches("essence")).isFalse();
|
||||
assertThat(matcher.matches("")).isTrue();
|
||||
|
||||
matcher = StringMatcher.forPrefix("Ess", false);
|
||||
assertThat(matcher.matches("elite")).isFalse();
|
||||
assertThat(matcher.matches("ess")).isFalse();
|
||||
assertThat(matcher.matches("")).isFalse();
|
||||
assertThat(matcher.matches("e")).isFalse();
|
||||
assertThat(matcher.matches("essential")).isFalse();
|
||||
assertThat(matcher.matches("Essence")).isTrue();
|
||||
assertThat(matcher.matches("essence")).isFalse();
|
||||
assertThat(matcher.matches((String)null)).isFalse();
|
||||
matcher = StringMatcher.forPrefix("Ess", true);
|
||||
assertThat(matcher.matches("esSEncE")).isTrue();
|
||||
assertThat(matcher.matches("ess")).isTrue();
|
||||
assertThat(matcher.matches("ES")).isFalse();
|
||||
matcher = StringMatcher.forPrefix("", false);
|
||||
assertThat(matcher.matches("elite")).isTrue();
|
||||
|
||||
matcher = StringMatcher.forSuffix("ess", false);
|
||||
assertThat(matcher.matches("elite")).isFalse();
|
||||
assertThat(matcher.matches("es")).isFalse();
|
||||
assertThat(matcher.matches("")).isFalse();
|
||||
assertThat(matcher.matches("ess")).isTrue();
|
||||
assertThat(matcher.matches("Excess")).isTrue();
|
||||
assertThat(matcher.matches("ExcesS")).isFalse();
|
||||
assertThat(matcher.matches((String)null)).isFalse();
|
||||
matcher = StringMatcher.forSuffix("ess", true);
|
||||
assertThat(matcher.matches("esSEncESs")).isTrue();
|
||||
assertThat(matcher.matches("ess")).isTrue();
|
||||
matcher = StringMatcher.forSuffix("", true);
|
||||
assertThat(matcher.matches("")).isTrue();
|
||||
assertThat(matcher.matches("any")).isTrue();
|
||||
|
||||
matcher = StringMatcher.forContains("ess");
|
||||
assertThat(matcher.matches("elite")).isFalse();
|
||||
assertThat(matcher.matches("es")).isFalse();
|
||||
assertThat(matcher.matches("")).isFalse();
|
||||
assertThat(matcher.matches("essence")).isTrue();
|
||||
assertThat(matcher.matches("eSs")).isFalse();
|
||||
assertThat(matcher.matches("ExcesS")).isFalse();
|
||||
assertThat(matcher.matches((String)null)).isFalse();
|
||||
|
||||
matcher = StringMatcher.forSafeRegEx(Pattern.compile("^es*.*"));
|
||||
assertThat(matcher.matches("essence")).isTrue();
|
||||
assertThat(matcher.matches("")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headerMatcher() {
|
||||
HeaderMatcher matcher = HeaderMatcher.forExactValue("version", "v1", false);
|
||||
assertThat(matcher.matches("v1")).isTrue();
|
||||
assertThat(matcher.matches("v2")).isFalse();
|
||||
|
||||
matcher = HeaderMatcher.forExactValue("version", "v1", true);
|
||||
assertThat(matcher.matches("v1")).isFalse();
|
||||
assertThat(matcher.matches( "v2")).isTrue();
|
||||
|
||||
matcher = HeaderMatcher.forPresent("version", true, false);
|
||||
assertThat(matcher.matches("any")).isTrue();
|
||||
assertThat(matcher.matches(null)).isFalse();
|
||||
matcher = HeaderMatcher.forPresent("version", true, true);
|
||||
assertThat(matcher.matches("version")).isFalse();
|
||||
matcher = HeaderMatcher.forPresent("version", false, true);
|
||||
assertThat(matcher.matches("tag")).isTrue();
|
||||
matcher = HeaderMatcher.forPresent("version", false, false);
|
||||
assertThat(matcher.matches("tag")).isFalse();
|
||||
|
||||
matcher = HeaderMatcher.forPrefix("version", "v2", false);
|
||||
assertThat(matcher.matches("v22")).isTrue();
|
||||
matcher = HeaderMatcher.forPrefix("version", "v2", true);
|
||||
assertThat(matcher.matches("v22")).isFalse();
|
||||
|
||||
matcher = HeaderMatcher.forSuffix("version", "v1", false);
|
||||
assertThat(matcher.matches("xv1")).isTrue();
|
||||
assertThat(matcher.matches("v1x")).isFalse();
|
||||
matcher = HeaderMatcher.forSuffix("version", "v2", true);
|
||||
assertThat(matcher.matches("xv1")).isTrue();
|
||||
assertThat(matcher.matches("1v2")).isFalse();
|
||||
|
||||
matcher = HeaderMatcher.forSafeRegEx("version", Pattern.compile("v2.*"), false);
|
||||
assertThat(matcher.matches("v2..")).isTrue();
|
||||
assertThat(matcher.matches("v1")).isFalse();
|
||||
matcher = HeaderMatcher.forSafeRegEx("version", Pattern.compile("v1\\..*"), true);
|
||||
assertThat(matcher.matches("v1.43")).isFalse();
|
||||
assertThat(matcher.matches("v2")).isTrue();
|
||||
|
||||
matcher = HeaderMatcher.forRange("version", Range.create(8080L, 8090L), false);
|
||||
assertThat(matcher.matches("8080")).isTrue();
|
||||
assertThat(matcher.matches("1")).isFalse();
|
||||
matcher = HeaderMatcher.forRange("version", Range.create(8080L, 8090L), true);
|
||||
assertThat(matcher.matches("1")).isTrue();
|
||||
assertThat(matcher.matches("8080")).isFalse();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,304 @@
|
|||
/*
|
||||
* Copyright 2021 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.grpc.xds.internal.rbac.engine;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import io.grpc.Attributes;
|
||||
import io.grpc.Grpc;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.MethodDescriptor;
|
||||
import io.grpc.MethodDescriptor.MethodType;
|
||||
import io.grpc.ServerCall;
|
||||
import io.grpc.internal.testing.TestUtils;
|
||||
import io.grpc.testing.TestMethodDescriptors;
|
||||
import io.grpc.xds.internal.Matchers;
|
||||
import io.grpc.xds.internal.Matchers.CidrMatcher;
|
||||
import io.grpc.xds.internal.Matchers.StringMatcher;
|
||||
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.Action;
|
||||
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AlwaysTrueMatcher;
|
||||
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AndMatcher;
|
||||
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AuthConfig;
|
||||
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AuthDecision;
|
||||
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.DestinationPortMatcher;
|
||||
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.HeaderMatcher;
|
||||
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.InvertMatcher;
|
||||
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.OrMatcher;
|
||||
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.PathMatcher;
|
||||
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.PolicyMatcher;
|
||||
import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.SourceIpMatcher;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.Principal;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class GrpcAuthorizationEngineTest {
|
||||
@Rule
|
||||
public final MockitoRule mocks = MockitoJUnit.rule();
|
||||
|
||||
private static final String POLICY_NAME = "policy-name";
|
||||
private static final String HEADER_KEY = "header-key";
|
||||
private static final String HEADER_VALUE = "header-val";
|
||||
private static final String IP_ADDR1 = "10.10.10.0";
|
||||
private static final String IP_ADDR2 = "68.36.0.19";
|
||||
private static final int PORT = 100;
|
||||
private static final String PATH = "/auth/engine";
|
||||
private static final StringMatcher STRING_MATCHER = StringMatcher.forExact("/" + PATH, false);
|
||||
private static final Metadata HEADER = metadata(HEADER_KEY, HEADER_VALUE);
|
||||
|
||||
@Mock
|
||||
private ServerCall<Void,Void> serverCall;
|
||||
@Mock
|
||||
private SSLSession sslSession;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
X509Certificate[] certs = {TestUtils.loadX509Cert("server1.pem")};
|
||||
when(sslSession.getPeerCertificates()).thenReturn(certs);
|
||||
Attributes attributes = Attributes.newBuilder()
|
||||
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, new InetSocketAddress(IP_ADDR2, PORT))
|
||||
.set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, new InetSocketAddress(IP_ADDR1, PORT))
|
||||
.set(Grpc.TRANSPORT_ATTR_SSL_SESSION, sslSession)
|
||||
.build();
|
||||
when(serverCall.getAttributes()).thenReturn(attributes);
|
||||
when(serverCall.getMethodDescriptor()).thenReturn(method().build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ipMatcher() throws Exception {
|
||||
CidrMatcher ip1 = CidrMatcher.create(InetAddress.getByName(IP_ADDR1), 24);
|
||||
DestinationIpMatcher destIpMatcher = new DestinationIpMatcher(ip1);
|
||||
CidrMatcher ip2 = CidrMatcher.create(InetAddress.getByName(IP_ADDR2), 24);
|
||||
SourceIpMatcher sourceIpMatcher = new SourceIpMatcher(ip2);
|
||||
DestinationPortMatcher portMatcher = new DestinationPortMatcher(PORT);
|
||||
OrMatcher permission = OrMatcher.create(AndMatcher.create(portMatcher, destIpMatcher));
|
||||
OrMatcher principal = OrMatcher.create(sourceIpMatcher);
|
||||
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);
|
||||
|
||||
Attributes attributes = Attributes.newBuilder()
|
||||
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, new InetSocketAddress(IP_ADDR2, PORT))
|
||||
.set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, new InetSocketAddress(IP_ADDR1, 2))
|
||||
.build();
|
||||
when(serverCall.getAttributes()).thenReturn(attributes);
|
||||
decision = engine.evaluate(HEADER, serverCall);
|
||||
assertThat(decision.decision()).isEqualTo(Action.DENY);
|
||||
assertThat(decision.matchingPolicyName()).isEqualTo(null);
|
||||
|
||||
attributes = Attributes.newBuilder()
|
||||
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, null)
|
||||
.set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, new InetSocketAddress("1.1.1.1", PORT))
|
||||
.build();
|
||||
when(serverCall.getAttributes()).thenReturn(attributes);
|
||||
decision = engine.evaluate(HEADER, serverCall);
|
||||
assertThat(decision.decision()).isEqualTo(Action.DENY);
|
||||
assertThat(decision.matchingPolicyName()).isEqualTo(null);
|
||||
|
||||
engine = new GrpcAuthorizationEngine(
|
||||
new AuthConfig(Collections.singletonList(policyMatcher), Action.DENY));
|
||||
decision = engine.evaluate(HEADER, serverCall);
|
||||
assertThat(decision.decision()).isEqualTo(Action.ALLOW);
|
||||
assertThat(decision.matchingPolicyName()).isEqualTo(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headerMatcher() {
|
||||
HeaderMatcher headerMatcher = new HeaderMatcher(Matchers.HeaderMatcher
|
||||
.forExactValue(HEADER_KEY, HEADER_VALUE, 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);
|
||||
|
||||
HEADER.put(Metadata.Key.of(HEADER_KEY, Metadata.ASCII_STRING_MARSHALLER), HEADER_VALUE);
|
||||
headerMatcher = new HeaderMatcher(Matchers.HeaderMatcher
|
||||
.forExactValue(HEADER_KEY, HEADER_VALUE + "," + HEADER_VALUE, false));
|
||||
principal = OrMatcher.create(headerMatcher);
|
||||
policyMatcher = new PolicyMatcher(POLICY_NAME,
|
||||
OrMatcher.create(AlwaysTrueMatcher.INSTANCE), principal);
|
||||
engine = new GrpcAuthorizationEngine(
|
||||
new AuthConfig(Collections.singletonList(policyMatcher), Action.ALLOW));
|
||||
decision = engine.evaluate(HEADER, serverCall);
|
||||
assertThat(decision.decision()).isEqualTo(Action.ALLOW);
|
||||
|
||||
headerMatcher = new HeaderMatcher(Matchers.HeaderMatcher
|
||||
.forExactValue(HEADER_KEY + Metadata.BINARY_HEADER_SUFFIX, HEADER_VALUE, false));
|
||||
principal = OrMatcher.create(headerMatcher);
|
||||
policyMatcher = new PolicyMatcher(POLICY_NAME,
|
||||
OrMatcher.create(AlwaysTrueMatcher.INSTANCE), principal);
|
||||
engine = new GrpcAuthorizationEngine(
|
||||
new AuthConfig(Collections.singletonList(policyMatcher), Action.ALLOW));
|
||||
decision = engine.evaluate(HEADER, serverCall);
|
||||
assertThat(decision.decision()).isEqualTo(Action.DENY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pathMatcher() {
|
||||
PathMatcher pathMatcher = new PathMatcher(STRING_MATCHER);
|
||||
OrMatcher permission = OrMatcher.create(AlwaysTrueMatcher.INSTANCE);
|
||||
OrMatcher principal = OrMatcher.create(pathMatcher);
|
||||
PolicyMatcher policyMatcher = new PolicyMatcher(POLICY_NAME, permission, principal);
|
||||
GrpcAuthorizationEngine engine = new GrpcAuthorizationEngine(
|
||||
new AuthConfig(Collections.singletonList(policyMatcher), Action.DENY));
|
||||
AuthDecision decision = engine.evaluate(HEADER, serverCall);
|
||||
assertThat(decision.decision()).isEqualTo(Action.DENY);
|
||||
assertThat(decision.matchingPolicyName()).isEqualTo(POLICY_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticatedMatcher() throws Exception {
|
||||
AuthenticatedMatcher authMatcher = new AuthenticatedMatcher(
|
||||
StringMatcher.forExact("*.test.google.fr", false));
|
||||
PathMatcher pathMatcher = new PathMatcher(STRING_MATCHER);
|
||||
OrMatcher permission = OrMatcher.create(authMatcher);
|
||||
OrMatcher principal = OrMatcher.create(pathMatcher);
|
||||
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);
|
||||
|
||||
X509Certificate[] certs = {TestUtils.loadX509Cert("badserver.pem")};
|
||||
when(sslSession.getPeerCertificates()).thenReturn(certs);
|
||||
decision = engine.evaluate(HEADER, serverCall);
|
||||
assertThat(decision.decision()).isEqualTo(Action.DENY);
|
||||
assertThat(decision.matchingPolicyName()).isEqualTo(null);
|
||||
|
||||
X509Certificate mockCert = mock(X509Certificate.class);
|
||||
when(sslSession.getPeerCertificates()).thenReturn(new X509Certificate[]{mockCert});
|
||||
assertThat(engine.evaluate(HEADER, serverCall).decision()).isEqualTo(Action.DENY);
|
||||
when(mockCert.getSubjectDN()).thenReturn(mock(Principal.class));
|
||||
assertThat(engine.evaluate(HEADER, serverCall).decision()).isEqualTo(Action.DENY);
|
||||
when(mockCert.getSubjectAlternativeNames()).thenReturn(Arrays.<List<?>>asList(
|
||||
Arrays.asList(2, "*.test.google.fr")));
|
||||
assertThat(engine.evaluate(HEADER, serverCall).decision()).isEqualTo(Action.ALLOW);
|
||||
when(mockCert.getSubjectAlternativeNames()).thenReturn(Arrays.<List<?>>asList(
|
||||
Arrays.asList(6, "*.test.google.fr")));
|
||||
assertThat(engine.evaluate(HEADER, serverCall).decision()).isEqualTo(Action.ALLOW);
|
||||
when(mockCert.getSubjectAlternativeNames()).thenReturn(Arrays.<List<?>>asList(
|
||||
Arrays.asList(10, "*.test.google.fr")));
|
||||
assertThat(engine.evaluate(HEADER, serverCall).decision()).isEqualTo(Action.DENY);
|
||||
when(mockCert.getSubjectAlternativeNames()).thenReturn(Arrays.<List<?>>asList(
|
||||
Arrays.asList(2, "google.com"), Arrays.asList(6, "*.test.google.fr")));
|
||||
assertThat(engine.evaluate(HEADER, serverCall).decision()).isEqualTo(Action.ALLOW);
|
||||
when(mockCert.getSubjectAlternativeNames()).thenReturn(Arrays.<List<?>>asList(
|
||||
Arrays.asList(6, "*.test.google.fr"), Arrays.asList(2, "google.com")));
|
||||
assertThat(engine.evaluate(HEADER, serverCall).decision()).isEqualTo(Action.ALLOW);
|
||||
when(mockCert.getSubjectAlternativeNames()).thenReturn(Arrays.<List<?>>asList(
|
||||
Arrays.asList(2, "*.test.google.fr"), Arrays.asList(6, "google.com")));
|
||||
assertThat(engine.evaluate(HEADER, serverCall).decision()).isEqualTo(Action.DENY);
|
||||
when(mockCert.getSubjectAlternativeNames()).thenReturn(Arrays.<List<?>>asList(
|
||||
Arrays.asList(2, "*.test.google.fr"), Arrays.asList(6, "google.com"),
|
||||
Arrays.asList(6, "*.test.google.fr")));
|
||||
assertThat(engine.evaluate(HEADER, serverCall).decision()).isEqualTo(Action.ALLOW);
|
||||
|
||||
// match any authenticated connection if StringMatcher not set in AuthenticatedMatcher
|
||||
permission = OrMatcher.create(new AuthenticatedMatcher(null));
|
||||
policyMatcher = new PolicyMatcher(POLICY_NAME, permission, principal);
|
||||
when(mockCert.getSubjectAlternativeNames()).thenReturn(
|
||||
Arrays.<List<?>>asList(Arrays.asList(6, "random")));
|
||||
engine = new GrpcAuthorizationEngine(new AuthConfig(Collections.singletonList(policyMatcher),
|
||||
Action.ALLOW));
|
||||
assertThat(engine.evaluate(HEADER, serverCall).decision()).isEqualTo(Action.ALLOW);
|
||||
|
||||
// not match any unauthenticated connection
|
||||
Attributes attributes = Attributes.newBuilder()
|
||||
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, new InetSocketAddress(IP_ADDR2, PORT))
|
||||
.set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, new InetSocketAddress(IP_ADDR1, PORT))
|
||||
.build();
|
||||
when(serverCall.getAttributes()).thenReturn(attributes);
|
||||
assertThat(engine.evaluate(HEADER, serverCall).decision()).isEqualTo(Action.DENY);
|
||||
|
||||
doThrow(new SSLPeerUnverifiedException("bad")).when(sslSession).getPeerCertificates();
|
||||
decision = engine.evaluate(HEADER, serverCall);
|
||||
assertThat(decision.decision()).isEqualTo(Action.DENY);
|
||||
assertThat(decision.matchingPolicyName()).isEqualTo(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multiplePolicies() throws Exception {
|
||||
AuthenticatedMatcher authMatcher = new AuthenticatedMatcher(
|
||||
StringMatcher.forSuffix("TEST.google.fr", true));
|
||||
PathMatcher pathMatcher = new PathMatcher(STRING_MATCHER);
|
||||
OrMatcher principal = OrMatcher.create(AndMatcher.create(authMatcher, pathMatcher));
|
||||
OrMatcher permission = OrMatcher.create(AndMatcher.create(pathMatcher,
|
||||
new InvertMatcher(new DestinationPortMatcher(PORT + 1))));
|
||||
PolicyMatcher policyMatcher1 = new PolicyMatcher(POLICY_NAME, permission, principal);
|
||||
|
||||
HeaderMatcher headerMatcher = new HeaderMatcher(Matchers.HeaderMatcher
|
||||
.forExactValue(HEADER_KEY, HEADER_VALUE + 1, false));
|
||||
authMatcher = new AuthenticatedMatcher(
|
||||
StringMatcher.forContains("TEST.google.fr"));
|
||||
principal = OrMatcher.create(headerMatcher, authMatcher);
|
||||
CidrMatcher ip1 = CidrMatcher.create(InetAddress.getByName(IP_ADDR1), 24);
|
||||
DestinationIpMatcher destIpMatcher = new DestinationIpMatcher(ip1);
|
||||
permission = OrMatcher.create(destIpMatcher, pathMatcher);
|
||||
PolicyMatcher policyMatcher2 = new PolicyMatcher(POLICY_NAME + "-2", permission, principal);
|
||||
|
||||
GrpcAuthorizationEngine engine = new GrpcAuthorizationEngine(
|
||||
new AuthConfig(ImmutableList.of(policyMatcher1, policyMatcher2), Action.DENY));
|
||||
AuthDecision decision = engine.evaluate(HEADER, serverCall);
|
||||
assertThat(decision.decision()).isEqualTo(Action.DENY);
|
||||
assertThat(decision.matchingPolicyName()).isEqualTo(POLICY_NAME);
|
||||
}
|
||||
|
||||
private MethodDescriptor.Builder<Void, Void> method() {
|
||||
return MethodDescriptor.<Void,Void>newBuilder()
|
||||
.setType(MethodType.BIDI_STREAMING)
|
||||
.setFullMethodName(PATH)
|
||||
.setRequestMarshaller(TestMethodDescriptors.voidMarshaller())
|
||||
.setResponseMarshaller(TestMethodDescriptors.voidMarshaller());
|
||||
}
|
||||
|
||||
private static Metadata metadata(String key, String value) {
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.put(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER), value);
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue