mirror of https://github.com/grpc/grpc-java.git
Static authorization server interceptor implementation (#8934)
* Static authorization server interceptor implementation * Resolving comments * Remove RbacParser file * update error logs * checkstyle fixes * Add InternalRbacFilter * formatting * javadoc * format test file * resolving comments * minor formatting * Update comment
This commit is contained in:
parent
530cf905b1
commit
0194ae9a41
|
|
@ -33,12 +33,6 @@ tasks.named("jar").configure {
|
|||
classifier = 'original'
|
||||
}
|
||||
|
||||
// TODO(ashithasantosh): Remove javadoc exclusion on adding authorization
|
||||
// interceptor implementations.
|
||||
tasks.named("javadoc").configure {
|
||||
exclude "io/grpc/authz/*"
|
||||
}
|
||||
|
||||
tasks.named("shadowJar").configure {
|
||||
classifier = null
|
||||
dependencies {
|
||||
|
|
@ -52,6 +46,12 @@ tasks.named("shadowJar").configure {
|
|||
relocate 'com.google.api.expr', 'io.grpc.xds.shaded.com.google.api.expr'
|
||||
}
|
||||
|
||||
tasks.named("compileJava").configure {
|
||||
it.options.compilerArgs += [
|
||||
"-Xlint:-processing",
|
||||
]
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
maven(MavenPublication) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright 2022 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.authz;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import io.envoyproxy.envoy.config.rbac.v3.RBAC;
|
||||
import io.grpc.ExperimentalApi;
|
||||
import io.grpc.InternalServerInterceptors;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.ServerCall;
|
||||
import io.grpc.ServerCallHandler;
|
||||
import io.grpc.ServerInterceptor;
|
||||
import io.grpc.xds.InternalRbacFilter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Authorization server interceptor for static policy. The class will get
|
||||
* <a href="https://github.com/grpc/proposal/blob/master/A43-grpc-authorization-api.md#user-facing-authorization-policy">
|
||||
* gRPC Authorization policy</a> as a JSON string during initialization.
|
||||
* This policy will be translated to Envoy RBAC policies to make
|
||||
* authorization decisions. The policy cannot be changed once created.
|
||||
*/
|
||||
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/9746")
|
||||
public final class AuthorizationServerInterceptor implements ServerInterceptor {
|
||||
private final List<ServerInterceptor> interceptors = new ArrayList<>();
|
||||
|
||||
private AuthorizationServerInterceptor(String authorizationPolicy)
|
||||
throws IOException {
|
||||
List<RBAC> rbacs = AuthorizationPolicyTranslator.translate(authorizationPolicy);
|
||||
if (rbacs == null || rbacs.isEmpty() || rbacs.size() > 2) {
|
||||
throw new IllegalArgumentException("Failed to translate authorization policy");
|
||||
}
|
||||
for (RBAC rbac: rbacs) {
|
||||
interceptors.add(
|
||||
InternalRbacFilter.createInterceptor(
|
||||
io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC.newBuilder()
|
||||
.setRules(rbac).build()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
|
||||
ServerCall<ReqT, RespT> call, Metadata headers,
|
||||
ServerCallHandler<ReqT, RespT> next) {
|
||||
for (ServerInterceptor interceptor: interceptors) {
|
||||
next = InternalServerInterceptors.interceptCallHandlerCreate(interceptor, next);
|
||||
}
|
||||
return next.startCall(call, headers);
|
||||
}
|
||||
|
||||
// Static method that creates an AuthorizationServerInterceptor.
|
||||
public static AuthorizationServerInterceptor create(String authorizationPolicy)
|
||||
throws IOException {
|
||||
checkNotNull(authorizationPolicy, "authorizationPolicy");
|
||||
return new AuthorizationServerInterceptor(authorizationPolicy);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,375 @@
|
|||
/*
|
||||
* Copyright 2022 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.authz;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import io.grpc.ChannelCredentials;
|
||||
import io.grpc.Grpc;
|
||||
import io.grpc.InsecureChannelCredentials;
|
||||
import io.grpc.InsecureServerCredentials;
|
||||
import io.grpc.ManagedChannel;
|
||||
import io.grpc.Server;
|
||||
import io.grpc.ServerCredentials;
|
||||
import io.grpc.StatusRuntimeException;
|
||||
import io.grpc.TlsChannelCredentials;
|
||||
import io.grpc.TlsServerCredentials;
|
||||
import io.grpc.TlsServerCredentials.ClientAuth;
|
||||
import io.grpc.internal.testing.TestUtils;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
import io.grpc.testing.protobuf.SimpleRequest;
|
||||
import io.grpc.testing.protobuf.SimpleResponse;
|
||||
import io.grpc.testing.protobuf.SimpleServiceGrpc;
|
||||
|
||||
import java.io.File;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class AuthorizationEnd2EndTest {
|
||||
public static final String SERVER_0_KEY_FILE = "server0.key";
|
||||
public static final String SERVER_0_PEM_FILE = "server0.pem";
|
||||
public static final String CLIENT_0_KEY_FILE = "client.key";
|
||||
public static final String CLIENT_0_PEM_FILE = "client.pem";
|
||||
public static final String CA_PEM_FILE = "ca.pem";
|
||||
|
||||
private Server server;
|
||||
private ManagedChannel channel;
|
||||
|
||||
private void initServerWithStaticAuthz(
|
||||
String authorizationPolicy, ServerCredentials serverCredentials) throws Exception {
|
||||
AuthorizationServerInterceptor authzInterceptor =
|
||||
AuthorizationServerInterceptor.create(authorizationPolicy);
|
||||
server = Grpc.newServerBuilderForPort(0, serverCredentials)
|
||||
.addService(new SimpleServiceImpl())
|
||||
.intercept(authzInterceptor)
|
||||
.build()
|
||||
.start();
|
||||
}
|
||||
|
||||
private SimpleServiceGrpc.SimpleServiceBlockingStub getStub() {
|
||||
channel =
|
||||
Grpc.newChannelBuilderForAddress(
|
||||
"localhost", server.getPort(), InsecureChannelCredentials.create())
|
||||
.build();
|
||||
return SimpleServiceGrpc.newBlockingStub(channel);
|
||||
}
|
||||
|
||||
private SimpleServiceGrpc.SimpleServiceBlockingStub getStub(
|
||||
ChannelCredentials channelCredentials) {
|
||||
channel = Grpc.newChannelBuilderForAddress(
|
||||
"localhost", server.getPort(), channelCredentials)
|
||||
.overrideAuthority("foo.test.google.com.au")
|
||||
.build();
|
||||
return SimpleServiceGrpc.newBlockingStub(channel);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
if (server != null) {
|
||||
server.shutdown();
|
||||
}
|
||||
if (channel != null) {
|
||||
channel.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void staticAuthzAllowsRpcNoMatchInDenyMatchInAllowTest() throws Exception {
|
||||
String policy = "{"
|
||||
+ " \"name\" : \"authz\" ,"
|
||||
+ " \"deny_rules\": ["
|
||||
+ " {"
|
||||
+ " \"name\": \"deny_UnaryRpc\","
|
||||
+ " \"request\": {"
|
||||
+ " \"paths\": ["
|
||||
+ " \"*/UnaryRpc\""
|
||||
+ " ],"
|
||||
+ " \"headers\": ["
|
||||
+ " {"
|
||||
+ " \"key\": \"dev-path\","
|
||||
+ " \"values\": [\"/dev/path/*\"]"
|
||||
+ " }"
|
||||
+ " ]"
|
||||
+ " }"
|
||||
+ " }"
|
||||
+ " ],"
|
||||
+ " \"allow_rules\": ["
|
||||
+ " {"
|
||||
+ " \"name\": \"allow_all\""
|
||||
+ " }"
|
||||
+ " ]"
|
||||
+ "}";
|
||||
initServerWithStaticAuthz(policy, InsecureServerCredentials.create());
|
||||
getStub().unaryRpc(SimpleRequest.getDefaultInstance());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void staticAuthzDeniesRpcNoMatchInDenyAndAllowTest() throws Exception {
|
||||
String policy = "{"
|
||||
+ " \"name\" : \"authz\" ,"
|
||||
+ " \"deny_rules\": ["
|
||||
+ " {"
|
||||
+ " \"name\": \"deny_foo\","
|
||||
+ " \"source\": {"
|
||||
+ " \"principals\": ["
|
||||
+ " \"foo\""
|
||||
+ " ]"
|
||||
+ " }"
|
||||
+ " }"
|
||||
+ " ],"
|
||||
+ " \"allow_rules\": ["
|
||||
+ " {"
|
||||
+ " \"name\": \"allow_ClientStreamingRpc\","
|
||||
+ " \"request\": {"
|
||||
+ " \"paths\": ["
|
||||
+ " \"*/ClientStreamingRpc\""
|
||||
+ " ]"
|
||||
+ " }"
|
||||
+ " }"
|
||||
+ " ]"
|
||||
+ "}";
|
||||
initServerWithStaticAuthz(policy, InsecureServerCredentials.create());
|
||||
try {
|
||||
getStub().unaryRpc(SimpleRequest.getDefaultInstance());
|
||||
fail("exception expected");
|
||||
} catch (StatusRuntimeException sre) {
|
||||
assertThat(sre).hasMessageThat().isEqualTo(
|
||||
"PERMISSION_DENIED: Access Denied");
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("the test failed ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void staticAuthzDeniesRpcMatchInDenyAndAllowTest() throws Exception {
|
||||
String policy = "{"
|
||||
+ " \"name\" : \"authz\" ,"
|
||||
+ " \"deny_rules\": ["
|
||||
+ " {"
|
||||
+ " \"name\": \"deny_UnaryRpc\","
|
||||
+ " \"request\": {"
|
||||
+ " \"paths\": ["
|
||||
+ " \"*/UnaryRpc\""
|
||||
+ " ]"
|
||||
+ " }"
|
||||
+ " }"
|
||||
+ " ],"
|
||||
+ " \"allow_rules\": ["
|
||||
+ " {"
|
||||
+ " \"name\": \"allow_UnaryRpc\","
|
||||
+ " \"request\": {"
|
||||
+ " \"paths\": ["
|
||||
+ " \"*/UnaryRpc\""
|
||||
+ " ]"
|
||||
+ " }"
|
||||
+ " }"
|
||||
+ " ]"
|
||||
+ "}";
|
||||
initServerWithStaticAuthz(policy, InsecureServerCredentials.create());
|
||||
try {
|
||||
getStub().unaryRpc(SimpleRequest.getDefaultInstance());
|
||||
fail("exception expected");
|
||||
} catch (StatusRuntimeException sre) {
|
||||
assertThat(sre).hasMessageThat().isEqualTo(
|
||||
"PERMISSION_DENIED: Access Denied");
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("the test failed ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void staticAuthzDeniesRpcMatchInDenyNoMatchInAllowTest() throws Exception {
|
||||
String policy = "{"
|
||||
+ " \"name\" : \"authz\" ,"
|
||||
+ " \"deny_rules\": ["
|
||||
+ " {"
|
||||
+ " \"name\": \"deny_UnaryRpc\","
|
||||
+ " \"request\": {"
|
||||
+ " \"paths\": ["
|
||||
+ " \"*/UnaryRpc\""
|
||||
+ " ]"
|
||||
+ " }"
|
||||
+ " }"
|
||||
+ " ],"
|
||||
+ " \"allow_rules\": ["
|
||||
+ " {"
|
||||
+ " \"name\": \"allow_ClientStreamingRpc\","
|
||||
+ " \"request\": {"
|
||||
+ " \"paths\": ["
|
||||
+ " \"*/ClientStreamingRpc\""
|
||||
+ " ]"
|
||||
+ " }"
|
||||
+ " }"
|
||||
+ " ]"
|
||||
+ "}";
|
||||
initServerWithStaticAuthz(policy, InsecureServerCredentials.create());
|
||||
try {
|
||||
getStub().unaryRpc(SimpleRequest.getDefaultInstance());
|
||||
fail("exception expected");
|
||||
} catch (StatusRuntimeException sre) {
|
||||
assertThat(sre).hasMessageThat().isEqualTo(
|
||||
"PERMISSION_DENIED: Access Denied");
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("the test failed ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void staticAuthzAllowsRpcEmptyDenyMatchInAllowTest() throws Exception {
|
||||
String policy = "{"
|
||||
+ " \"name\" : \"authz\" ,"
|
||||
+ " \"allow_rules\": ["
|
||||
+ " {"
|
||||
+ " \"name\": \"allow_UnaryRpc\","
|
||||
+ " \"request\": {"
|
||||
+ " \"paths\": ["
|
||||
+ " \"*/UnaryRpc\""
|
||||
+ " ]"
|
||||
+ " }"
|
||||
+ " }"
|
||||
+ " ]"
|
||||
+ "}";
|
||||
initServerWithStaticAuthz(policy, InsecureServerCredentials.create());
|
||||
getStub().unaryRpc(SimpleRequest.getDefaultInstance());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void staticAuthzDeniesRpcEmptyDenyNoMatchInAllowTest() throws Exception {
|
||||
String policy = "{"
|
||||
+ " \"name\" : \"authz\" ,"
|
||||
+ " \"allow_rules\": ["
|
||||
+ " {"
|
||||
+ " \"name\": \"allow_ClientStreamingRpc\","
|
||||
+ " \"request\": {"
|
||||
+ " \"paths\": ["
|
||||
+ " \"*/ClientStreamingRpc\""
|
||||
+ " ]"
|
||||
+ " }"
|
||||
+ " }"
|
||||
+ " ]"
|
||||
+ "}";
|
||||
initServerWithStaticAuthz(policy, InsecureServerCredentials.create());
|
||||
try {
|
||||
getStub().unaryRpc(SimpleRequest.getDefaultInstance());
|
||||
fail("exception expected");
|
||||
} catch (StatusRuntimeException sre) {
|
||||
assertThat(sre).hasMessageThat().isEqualTo(
|
||||
"PERMISSION_DENIED: Access Denied");
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("the test failed ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void staticAuthzDeniesRpcWithPrincipalsFieldOnUnauthenticatedConnectionTest()
|
||||
throws Exception {
|
||||
String policy = "{"
|
||||
+ " \"name\" : \"authz\" ,"
|
||||
+ " \"allow_rules\": ["
|
||||
+ " {"
|
||||
+ " \"name\": \"allow_authenticated\","
|
||||
+ " \"source\": {"
|
||||
+ " \"principals\": [\"*\", \"\"]"
|
||||
+ " }"
|
||||
+ " }"
|
||||
+ " ]"
|
||||
+ "}";
|
||||
initServerWithStaticAuthz(policy, InsecureServerCredentials.create());
|
||||
try {
|
||||
getStub().unaryRpc(SimpleRequest.getDefaultInstance());
|
||||
fail("exception expected");
|
||||
} catch (StatusRuntimeException sre) {
|
||||
assertThat(sre).hasMessageThat().isEqualTo(
|
||||
"PERMISSION_DENIED: Access Denied");
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("the test failed ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void staticAuthzAllowsRpcWithPrincipalsFieldOnMtlsAuthenticatedConnectionTest()
|
||||
throws Exception {
|
||||
File caCertFile = TestUtils.loadCert(CA_PEM_FILE);
|
||||
File serverKey0File = TestUtils.loadCert(SERVER_0_KEY_FILE);
|
||||
File serverCert0File = TestUtils.loadCert(SERVER_0_PEM_FILE);
|
||||
File clientKey0File = TestUtils.loadCert(CLIENT_0_KEY_FILE);
|
||||
File clientCert0File = TestUtils.loadCert(CLIENT_0_PEM_FILE);
|
||||
String policy = "{"
|
||||
+ " \"name\" : \"authz\" ,"
|
||||
+ " \"allow_rules\": ["
|
||||
+ " {"
|
||||
+ " \"name\": \"allow_mtls\","
|
||||
+ " \"source\": {"
|
||||
+ " \"principals\": [\"*\"]"
|
||||
+ " }"
|
||||
+ " }"
|
||||
+ " ]"
|
||||
+ "}";
|
||||
ServerCredentials serverCredentials = TlsServerCredentials.newBuilder()
|
||||
.keyManager(serverCert0File, serverKey0File)
|
||||
.trustManager(caCertFile)
|
||||
.clientAuth(ClientAuth.REQUIRE)
|
||||
.build();
|
||||
initServerWithStaticAuthz(policy, serverCredentials);
|
||||
ChannelCredentials channelCredentials = TlsChannelCredentials.newBuilder()
|
||||
.keyManager(clientCert0File, clientKey0File)
|
||||
.trustManager(caCertFile)
|
||||
.build();
|
||||
getStub(channelCredentials).unaryRpc(SimpleRequest.getDefaultInstance());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void staticAuthzAllowsRpcWithPrincipalsFieldOnTlsAuthenticatedConnectionTest()
|
||||
throws Exception {
|
||||
File caCertFile = TestUtils.loadCert(CA_PEM_FILE);
|
||||
File serverKey0File = TestUtils.loadCert(SERVER_0_KEY_FILE);
|
||||
File serverCert0File = TestUtils.loadCert(SERVER_0_PEM_FILE);
|
||||
String policy = "{"
|
||||
+ " \"name\" : \"authz\" ,"
|
||||
+ " \"allow_rules\": ["
|
||||
+ " {"
|
||||
+ " \"name\": \"allow_tls\","
|
||||
+ " \"source\": {"
|
||||
+ " \"principals\": [\"\"]"
|
||||
+ " }"
|
||||
+ " }"
|
||||
+ " ]"
|
||||
+ "}";
|
||||
ServerCredentials serverCredentials = TlsServerCredentials.newBuilder()
|
||||
.keyManager(serverCert0File, serverKey0File)
|
||||
.trustManager(caCertFile)
|
||||
.clientAuth(ClientAuth.OPTIONAL)
|
||||
.build();
|
||||
initServerWithStaticAuthz(policy, serverCredentials);
|
||||
ChannelCredentials channelCredentials = TlsChannelCredentials.newBuilder()
|
||||
.trustManager(caCertFile)
|
||||
.build();
|
||||
getStub(channelCredentials).unaryRpc(SimpleRequest.getDefaultInstance());
|
||||
}
|
||||
|
||||
private static class SimpleServiceImpl extends SimpleServiceGrpc.SimpleServiceImplBase {
|
||||
@Override
|
||||
public void unaryRpc(SimpleRequest req, StreamObserver<SimpleResponse> respOb) {
|
||||
respOb.onNext(SimpleResponse.getDefaultInstance());
|
||||
respOb.onCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright 2022 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.authz;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class AuthorizationServerInterceptorTest {
|
||||
@Test
|
||||
public void invalidPolicyFailsStaticAuthzInterceptorCreation() throws Exception {
|
||||
String policy = "{ \"name\": \"abc\",, }";
|
||||
try {
|
||||
AuthorizationServerInterceptor.create(policy);
|
||||
fail("exception expected");
|
||||
} catch (IOException ioe) {
|
||||
assertThat(ioe).hasMessageThat().isEqualTo(
|
||||
"Use JsonReader.setLenient(true) to accept malformed JSON"
|
||||
+ " at line 1 column 18 path $.name");
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("the test failed ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validPolicyCreatesStaticAuthzInterceptor() throws Exception {
|
||||
String policy = "{"
|
||||
+ " \"name\" : \"authz\","
|
||||
+ " \"deny_rules\": ["
|
||||
+ " {"
|
||||
+ " \"name\": \"deny_foo\","
|
||||
+ " \"source\": {"
|
||||
+ " \"principals\": ["
|
||||
+ " \"spiffe://foo.com\""
|
||||
+ " ]"
|
||||
+ " }"
|
||||
+ " }"
|
||||
+ " ],"
|
||||
+ " \"allow_rules\": ["
|
||||
+ " {"
|
||||
+ " \"name\": \"allow_all\""
|
||||
+ " }"
|
||||
+ " ]"
|
||||
+ "}";
|
||||
assertNotNull(AuthorizationServerInterceptor.create(policy));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC;
|
||||
import io.grpc.Internal;
|
||||
import io.grpc.ServerInterceptor;
|
||||
import io.grpc.xds.RbacConfig;
|
||||
import io.grpc.xds.RbacFilter;
|
||||
|
||||
/** This class exposes some functionality in RbacFilter to other packages. */
|
||||
@Internal
|
||||
public final class InternalRbacFilter {
|
||||
|
||||
private InternalRbacFilter() {}
|
||||
|
||||
/** Parses RBAC filter config and creates AuthorizationServerInterceptor. */
|
||||
public static ServerInterceptor createInterceptor(RBAC rbac) {
|
||||
ConfigOrError<RbacConfig> filterConfig = RbacFilter.parseRbacConfig(rbac);
|
||||
if (filterConfig.errorDetail != null) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Failed to parse Rbac policy: %s", filterConfig.errorDetail));
|
||||
}
|
||||
return new RbacFilter().buildServerInterceptor(filterConfig.config, null);
|
||||
}
|
||||
}
|
||||
|
|
@ -54,6 +54,12 @@ public final class MatcherParser {
|
|||
case SUFFIX_MATCH:
|
||||
return Matchers.HeaderMatcher.forSuffix(
|
||||
proto.getName(), proto.getSuffixMatch(), proto.getInvertMatch());
|
||||
case CONTAINS_MATCH:
|
||||
return Matchers.HeaderMatcher.forContains(
|
||||
proto.getName(), proto.getContainsMatch(), proto.getInvertMatch());
|
||||
case STRING_MATCH:
|
||||
return Matchers.HeaderMatcher.forString(
|
||||
proto.getName(), parseStringMatcher(proto.getStringMatch()), proto.getInvertMatch());
|
||||
case HEADERMATCHSPECIFIER_NOT_SET:
|
||||
default:
|
||||
throw new IllegalArgumentException(
|
||||
|
|
|
|||
|
|
@ -62,6 +62,14 @@ public final class Matchers {
|
|||
@Nullable
|
||||
public abstract String suffix();
|
||||
|
||||
// Matches header value with the substring.
|
||||
@Nullable
|
||||
public abstract String contains();
|
||||
|
||||
// Matches header value with the string matcher.
|
||||
@Nullable
|
||||
public abstract StringMatcher stringMatcher();
|
||||
|
||||
// Whether the matching semantics is inverted. E.g., present && !inverted -> !present
|
||||
public abstract boolean inverted();
|
||||
|
||||
|
|
@ -69,50 +77,71 @@ public final class Matchers {
|
|||
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);
|
||||
return HeaderMatcher.create(
|
||||
name, exactValue, null, null, 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);
|
||||
return HeaderMatcher.create(
|
||||
name, null, safeRegEx, null, null, 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);
|
||||
return HeaderMatcher.create(name, null, null, range, null, null, 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);
|
||||
return HeaderMatcher.create(
|
||||
name, null, null, null, present, null, null, 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);
|
||||
return HeaderMatcher.create(name, null, null, null, null, prefix, null, null, 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);
|
||||
return HeaderMatcher.create(name, null, null, null, null, null, suffix, null, null, inverted);
|
||||
}
|
||||
|
||||
/** The request header value should have this substring. */
|
||||
public static HeaderMatcher forContains(String name, String contains, boolean inverted) {
|
||||
checkNotNull(name, "name");
|
||||
checkNotNull(contains, "contains");
|
||||
return HeaderMatcher.create(
|
||||
name, null, null, null, null, null, null, contains, null, inverted);
|
||||
}
|
||||
|
||||
/** The request header value should match this stringMatcher. */
|
||||
public static HeaderMatcher forString(
|
||||
String name, StringMatcher stringMatcher, boolean inverted) {
|
||||
checkNotNull(name, "name");
|
||||
checkNotNull(stringMatcher, "stringMatcher");
|
||||
return HeaderMatcher.create(
|
||||
name, null, null, null, null, null, null, null, stringMatcher, 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) {
|
||||
@Nullable String suffix, @Nullable String contains,
|
||||
@Nullable StringMatcher stringMatcher, boolean inverted) {
|
||||
checkNotNull(name, "name");
|
||||
return new AutoValue_Matchers_HeaderMatcher(name, exactValue, safeRegEx, range, present,
|
||||
prefix, suffix, inverted);
|
||||
prefix, suffix, contains, stringMatcher, inverted);
|
||||
}
|
||||
|
||||
/** Returns the matching result. */
|
||||
|
|
@ -138,8 +167,12 @@ public final class Matchers {
|
|||
baseMatch = value.startsWith(prefix());
|
||||
} else if (present() != null) {
|
||||
baseMatch = present();
|
||||
} else {
|
||||
} else if (suffix() != null) {
|
||||
baseMatch = value.endsWith(suffix());
|
||||
} else if (contains() != null) {
|
||||
baseMatch = value.contains(contains());
|
||||
} else {
|
||||
baseMatch = stringMatcher().matches(value);
|
||||
}
|
||||
return baseMatch != inverted();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,6 +163,15 @@ public class MatcherTest {
|
|||
assertThat(matcher.matches("1v2")).isFalse();
|
||||
assertThat(matcher.matches(null)).isFalse();
|
||||
|
||||
matcher = HeaderMatcher.forContains("version", "v1", false);
|
||||
assertThat(matcher.matches("xv1")).isTrue();
|
||||
assertThat(matcher.matches("1vx")).isFalse();
|
||||
assertThat(matcher.matches(null)).isFalse();
|
||||
matcher = HeaderMatcher.forContains("version", "v1", true);
|
||||
assertThat(matcher.matches("xv1")).isFalse();
|
||||
assertThat(matcher.matches("1vx")).isTrue();
|
||||
assertThat(matcher.matches(null)).isFalse();
|
||||
|
||||
matcher = HeaderMatcher.forSafeRegEx("version", Pattern.compile("v2.*"), false);
|
||||
assertThat(matcher.matches("v2..")).isTrue();
|
||||
assertThat(matcher.matches("v1")).isFalse();
|
||||
|
|
@ -180,5 +189,14 @@ public class MatcherTest {
|
|||
assertThat(matcher.matches("1")).isTrue();
|
||||
assertThat(matcher.matches("8080")).isFalse();
|
||||
assertThat(matcher.matches(null)).isFalse();
|
||||
|
||||
matcher = HeaderMatcher.forString("version", StringMatcher.forExact("v1", true), false);
|
||||
assertThat(matcher.matches("v1")).isTrue();
|
||||
assertThat(matcher.matches("v1x")).isFalse();
|
||||
assertThat(matcher.matches(null)).isFalse();
|
||||
matcher = HeaderMatcher.forString("version", StringMatcher.forExact("v1", true), true);
|
||||
assertThat(matcher.matches("v1x")).isTrue();
|
||||
assertThat(matcher.matches("v1")).isFalse();
|
||||
assertThat(matcher.matches(null)).isFalse();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue