diff --git a/java/grpc-client/src/main/java/io/vitess/client/grpc/GrpcClient.java b/java/grpc-client/src/main/java/io/vitess/client/grpc/GrpcClient.java index 9927068b9e1..282308c4660 100644 --- a/java/grpc-client/src/main/java/io/vitess/client/grpc/GrpcClient.java +++ b/java/grpc-client/src/main/java/io/vitess/client/grpc/GrpcClient.java @@ -20,6 +20,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; +import io.grpc.CallCredentials; import io.grpc.ManagedChannel; import io.grpc.StatusRuntimeException; import io.grpc.internal.WithLogId; @@ -87,15 +88,23 @@ public class GrpcClient implements RpcClient { public GrpcClient(ManagedChannel channel) { this.channel = channel; - if (channel instanceof WithLogId) { - channelId = ((WithLogId) channel).getLogId().toString(); - } else { - channelId = channel.toString(); - } + channelId = toChannelId(channel); asyncStub = VitessGrpc.newStub(channel); futureStub = VitessGrpc.newFutureStub(channel); } + public GrpcClient(ManagedChannel channel, CallCredentials credentials) { + this.channel = channel; + channelId = toChannelId(channel); + asyncStub = VitessGrpc.newStub(channel).withCallCredentials(credentials); + futureStub = VitessGrpc.newFutureStub(channel).withCallCredentials(credentials); + } + + private String toChannelId(ManagedChannel channel) { + return channel instanceof WithLogId ? + ((WithLogId) channel).getLogId().toString() : channel.toString(); + } + @Override public void close() throws IOException { channel.shutdown(); diff --git a/java/grpc-client/src/main/java/io/vitess/client/grpc/GrpcClientFactory.java b/java/grpc-client/src/main/java/io/vitess/client/grpc/GrpcClientFactory.java index d042e855420..5f9f4a54b0e 100644 --- a/java/grpc-client/src/main/java/io/vitess/client/grpc/GrpcClientFactory.java +++ b/java/grpc-client/src/main/java/io/vitess/client/grpc/GrpcClientFactory.java @@ -16,6 +16,8 @@ package io.vitess.client.grpc; +import io.grpc.CallCredentials; +import io.grpc.ManagedChannel; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -48,6 +50,7 @@ public class GrpcClientFactory implements RpcClientFactory { private RetryingInterceptorConfig config; + private CallCredentials callCredentials; public GrpcClientFactory() { this(RetryingInterceptorConfig.noOpConfig()); @@ -57,6 +60,11 @@ public GrpcClientFactory(RetryingInterceptorConfig config) { this.config = config; } + public GrpcClientFactory setCallCredentials(CallCredentials value) { + callCredentials = value; + return this; + } + /** * Factory method to construct a gRPC client connection with no transport-layer security. * @@ -67,8 +75,11 @@ public GrpcClientFactory(RetryingInterceptorConfig config) { */ @Override public RpcClient create(Context ctx, String target) { - return new GrpcClient( - NettyChannelBuilder.forTarget(target).negotiationType(NegotiationType.PLAINTEXT).intercept(new RetryingInterceptor(config)).build()); + ManagedChannel channel = NettyChannelBuilder.forTarget(target) + .negotiationType(NegotiationType.PLAINTEXT) + .intercept(new RetryingInterceptor(config)) + .build(); + return callCredentials != null ? new GrpcClient(channel, callCredentials) : new GrpcClient(channel); } /** diff --git a/java/grpc-client/src/main/java/io/vitess/client/grpc/StaticAuthCredentials.java b/java/grpc-client/src/main/java/io/vitess/client/grpc/StaticAuthCredentials.java new file mode 100644 index 00000000000..fbf5694b8c9 --- /dev/null +++ b/java/grpc-client/src/main/java/io/vitess/client/grpc/StaticAuthCredentials.java @@ -0,0 +1,47 @@ +package io.vitess.client.grpc; + +import io.grpc.Attributes; +import io.grpc.CallCredentials; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.Status; +import java.util.Objects; +import java.util.concurrent.Executor; + +/** + * {@link CallCredentials} that applies plain username and password. + */ +public class StaticAuthCredentials implements CallCredentials { + + private static final Metadata.Key USERNAME = + Metadata.Key.of("username", Metadata.ASCII_STRING_MARSHALLER); + private static final Metadata.Key PASSWORD = + Metadata.Key.of("password", Metadata.ASCII_STRING_MARSHALLER); + + private final String username; + private final String password; + + public StaticAuthCredentials(String username, String password) { + this.username = Objects.requireNonNull(username); + this.password = Objects.requireNonNull(password); + } + + @Override + public void applyRequestMetadata(MethodDescriptor method, Attributes attrs, + Executor executor, MetadataApplier applier) { + executor.execute(() -> { + try { + Metadata headers = new Metadata(); + headers.put(USERNAME, username); + headers.put(PASSWORD, password); + applier.apply(headers); + } catch (Throwable e) { + applier.fail(Status.UNAUTHENTICATED.withCause(e)); + } + }); + } + + @Override + public void thisUsesUnstableApi() { + } +} diff --git a/java/grpc-client/src/test/java/io/client/grpc/GrpcClientStaticAuthTest.java b/java/grpc-client/src/test/java/io/client/grpc/GrpcClientStaticAuthTest.java new file mode 100644 index 00000000000..8be2567535c --- /dev/null +++ b/java/grpc-client/src/test/java/io/client/grpc/GrpcClientStaticAuthTest.java @@ -0,0 +1,81 @@ +package io.client.grpc; + +import com.google.common.base.Throwables; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import io.vitess.client.Context; +import io.vitess.client.RpcClient; +import io.vitess.client.RpcClientTest; +import io.vitess.client.grpc.GrpcClientFactory; +import io.vitess.client.grpc.StaticAuthCredentials; +import io.vitess.proto.Vtgate.GetSrvKeyspaceRequest; +import java.net.ServerSocket; +import java.net.URI; +import java.util.Arrays; +import java.util.concurrent.ExecutionException; +import org.joda.time.Duration; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class GrpcClientStaticAuthTest extends RpcClientTest { + private static Process vtgateclienttest; + private static int port; + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + String vtRoot = System.getenv("VTROOT"); + if (vtRoot == null) { + throw new RuntimeException("cannot find env variable VTROOT; make sure to source dev.env"); + } + URI staticAuthFile = GrpcClientStaticAuthTest.class.getResource("grpc_static_auth.json").toURI(); + + ServerSocket socket = new ServerSocket(0); + port = socket.getLocalPort(); + socket.close(); + + vtgateclienttest = new ProcessBuilder(Arrays.asList( + vtRoot + "/bin/vtgateclienttest", + "-logtostderr", + "-grpc_port", Integer.toString(port), + "-service_map", "grpc-vtgateservice", + "-grpc_auth_mode", "static", + "-grpc_auth_static_password_file", staticAuthFile.getPath() + )).inheritIO().start(); + + Context ctx = Context.getDefault().withDeadlineAfter(Duration.millis(5000L)); + client = new GrpcClientFactory() + .setCallCredentials(new StaticAuthCredentials("test-username", "test-password")) + .create(ctx, "localhost:" + port); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + if (client != null) { + client.close(); + } + if (vtgateclienttest != null) { + vtgateclienttest.destroy(); + vtgateclienttest.waitFor(); + } + } + + @Test + public void testWrongPassword() throws Exception { + RpcClient client = new GrpcClientFactory() + .setCallCredentials(new StaticAuthCredentials("test-username", "WRONG-password")) + .create(Context.getDefault(), "localhost:" + port); + try { + client.getSrvKeyspace(Context.getDefault(), GetSrvKeyspaceRequest.getDefaultInstance()).get(); + Assert.fail(); + } catch (ExecutionException e) { + StatusRuntimeException cause = (StatusRuntimeException) Throwables.getRootCause(e); + Assert.assertSame(cause.getStatus().getCode(), Status.Code.PERMISSION_DENIED); + } + client.close(); + } +} diff --git a/java/grpc-client/src/test/resources/io/client/grpc/grpc_static_auth.json b/java/grpc-client/src/test/resources/io/client/grpc/grpc_static_auth.json new file mode 100644 index 00000000000..3d9941c5d33 --- /dev/null +++ b/java/grpc-client/src/test/resources/io/client/grpc/grpc_static_auth.json @@ -0,0 +1,6 @@ +[ + { + "username": "test-username", + "password": "test-password" + } +] diff --git a/java/pom.xml b/java/pom.xml index 3525af69f08..f253f5b773c 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -223,10 +223,10 @@ org.apache.maven.plugins maven-compiler-plugin - 3.3 + 3.8.0 - 1.7 - 1.7 + 1.8 + 1.8