Skip to content

Commit

Permalink
Enhance client-side gRPC TLS configuration
Browse files Browse the repository at this point in the history
Previously, utilizing JKS or P12 keystores and truststores in gRPC clients was restricted due to limitations inherent in legacy gRPC-Java-based clients. However, this commit expands upon the existing configuration by introducing support for JKS and P12 in the Quarkus (Vert.x based) gRPC client.

This commit also extends the tests for both clients and servers using various certificate formats and configuration (like mTLS)
  • Loading branch information
cescoffier committed Feb 29, 2024
1 parent 70d1c1f commit 44ba0d0
Show file tree
Hide file tree
Showing 62 changed files with 1,916 additions and 520 deletions.
4 changes: 2 additions & 2 deletions .github/native-tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@
},
{
"category": "gRPC",
"timeout": 70,
"test-modules": "grpc-health, grpc-interceptors, grpc-mutual-auth, grpc-plain-text-gzip, grpc-plain-text-mutiny, grpc-proto-v2, grpc-streaming, grpc-tls",
"timeout": 75,
"test-modules": "grpc-health, grpc-interceptors, grpc-mutual-auth, grpc-plain-text-gzip, grpc-plain-text-mutiny, grpc-proto-v2, grpc-streaming, grpc-tls, grpc-tls-p12",
"os-name": "ubuntu-latest"
},
{
Expand Down
2 changes: 1 addition & 1 deletion build-parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@
<dependency>
<groupId>me.escoffier.certs</groupId>
<artifactId>certificate-generator-junit5</artifactId>
<version>0.4.0</version>
<version>0.4.3</version>
<scope>test</scope>
</dependency>

Expand Down
232 changes: 232 additions & 0 deletions docs/src/main/asciidoc/grpc-reference.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
////
This guide is maintained in the main Quarkus repository
and pull requests should be submitted there:
https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc
////
= gRPC reference guide
include::_attributes.adoc[]
:categories: Serialization
:diataxis-type: Reference
:summary: Learn how to configure gRPC server and clients.
:topics: grpc
:extensions: io.quarkus:quarkus-grpc


== Using gRPC with Quarkus

If you need to implement a gRPC service or consume it, you need the `quarkus-grpc` extension.

Check warning on line 17 in docs/src/main/asciidoc/grpc-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'.", "location": {"path": "docs/src/main/asciidoc/grpc-reference.adoc", "range": {"start": {"line": 17, "column": 8}}}, "severity": "INFO"}
It handles both sides.

=== Using Maven

To enable gRPC, add the following dependency to your project:

[source,xml,subs=attributes+]
----
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-grpc</artifactId>
</dependency>
----

Next, ensure that the `generate-code` phase is enabled in the Quarkus Maven plugin:

[source,xml,subs=attributes+]
----
<plugin>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>
</plugin>
----

=== Using Gradle

For Gradle, add the following dependency to your project:

[source,gradle,subs=attributes+]
----
implementation 'io.quarkus:quarkus-grpc'
----

== Selecting a gRPC server

Quarkus provides two implementation of the gRPC server: gRPC Java (based on Netty) and Vert.x.
Both of them support TLS.

One of the advantage of the Vert.x based server is the ability to use a single server to handle HTTP requests and gRPC requests. This is useful if you want to expose both REST and gRPC endpoints on the same port. This is not possible with the gRPC Java server (using a separate server).

To select the gRPC server implementation, set the `quarkus.grpc.server.use-separate-server` property in your `application.properties` file:

[source,properties,subs=attributes+]
----
quarkus.grpc.server.use-separate-server=false # Use the Vert.x based server
----

We recommend the usage of the Vert.x based gRPC server, as it is more flexible and better integrated in the Quarkus ecosystem.

Check warning on line 76 in docs/src/main/asciidoc/grpc-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'using more direct instructions' rather than 'recommend'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'using more direct instructions' rather than 'recommend'.", "location": {"path": "docs/src/main/asciidoc/grpc-reference.adoc", "range": {"start": {"line": 76, "column": 4}}}, "severity": "INFO"}

Check warning on line 76 in docs/src/main/asciidoc/grpc-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'.", "location": {"path": "docs/src/main/asciidoc/grpc-reference.adoc", "range": {"start": {"line": 76, "column": 57}}}, "severity": "INFO"}

IMPORTANT: You cannot use both servers at the same time.

== Selecting gRPC clients

As for the server, Quarkus proposes two alternatives for the gRPC clients: gRPC Java and Vert.x.
Unlike for the server, you can select the transport for each client:

[source,properties,subs=attributes+]
----
quarkus.grpc.clients.hello.use-quarkus-grpc-client=true # Use client using the Vert.x based transport
----

Check warning on line 88 in docs/src/main/asciidoc/grpc-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'using more direct instructions' rather than 'recommend'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'using more direct instructions' rather than 'recommend'.", "location": {"path": "docs/src/main/asciidoc/grpc-reference.adoc", "range": {"start": {"line": 88, "column": 1}}}, "severity": "INFO"}

While it's not the default, we recommend using the Vert.x based client, as it is more flexible and better integrated in the Quarkus ecosystem.

Check warning on line 90 in docs/src/main/asciidoc/grpc-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'.", "location": {"path": "docs/src/main/asciidoc/grpc-reference.adoc", "range": {"start": {"line": 90, "column": 4}}}, "severity": "INFO"}

Check warning on line 90 in docs/src/main/asciidoc/grpc-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'.", "location": {"path": "docs/src/main/asciidoc/grpc-reference.adoc", "range": {"start": {"line": 90, "column": 36}}}, "severity": "INFO"}
It does not change the stubs you can use, as they are generated by the gRPC framework.
However, it changes the way the client communicates with the server.

== Configuring TLS for gRPC services

=== With the Vert.x based server

If you use the Vert.x based server, you can configure TLS by setting the following properties in your `application.properties` file:

[source,properties,subs=attributes+]
----
quarkus.grpc.server.use-separate-server=false
quarkus.grpc.server.plain-text=false
quarkus.http.ssl.certificate.key-store-file=target/certs/grpc-tls-keystore.p12
quarkus.http.ssl.certificate.key-store-password=*****
quarkus.http.insecure-requests=disabled
----

You can use `key-store-file` and `key-store-password` to configure the keystore file and its password when using JKS or P12. For PEM, use the `certificate` and `key` properties:

Check warning on line 110 in docs/src/main/asciidoc/grpc-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'.", "location": {"path": "docs/src/main/asciidoc/grpc-reference.adoc", "range": {"start": {"line": 110, "column": 107}}}, "severity": "INFO"}

[source,properties,subs=attributes+]
----
quarkus.grpc.server.use-separate-server=false
quarkus.grpc.server.plain-text=false
quarkus.http.ssl.certificate.files=target/certs/grpc-tls.crt
quarkus.http.ssl.certificate.key-files=target/certs/grpc-tls.key
quarkus.http.insecure-requests=disabled
----

NOTE: The `quarkus.http.insecure-requests` property is used to disable insecure requests.

NOTE: When TLS is enabled, it covers both HTTP and gRPC traffic.

=== With the gRPC Java server

If you use the gRPC Java server, you can configure TLS by setting the following properties in your `application.properties` file:

[source,properties,subs=attributes+]
----
quarkus.grpc.server.ssl.certificate=tls/server.pem
quarkus.grpc.server.ssl.key=tls/server.key
quarkus.grpc.server.plain-text=false
----

This server only supports `PEM` format for the certificate and the key.

== Configuring TLS for gRPC clients

When using the Vert.x based client, you can configure TLS by setting the following properties in your `application.properties` file:

Check warning on line 142 in docs/src/main/asciidoc/grpc-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'.", "location": {"path": "docs/src/main/asciidoc/grpc-reference.adoc", "range": {"start": {"line": 142, "column": 5}}}, "severity": "INFO"}

[source,properties,subs=attributes+]
----
quarkus.grpc.clients.hello.plain-text=false # Use TLS
quarkus.grpc.clients.hello.use-quarkus-grpc-client=true # Use client using the Vert.x based transport
quarkus.grpc.clients.hello.tls.enabled=true
quarkus.grpc.clients.hello.tls.trust-certificate-p12.path=target/certs/grpc-tls-truststore.jks
quarkus.grpc.clients.hello.tls.trust-certificate-p12.password=****
----

If you use JKS trust-store, use the following configuration:

[source,properties,subs=attributes+]
----
quarkus.grpc.clients.hello.plain-text=false # Use TLS
quarkus.grpc.clients.hello.use-quarkus-grpc-client=true # Use client using the Vert.x based transport
quarkus.grpc.clients.hello.tls.enabled=true
quarkus.grpc.clients.hello.tls.trust-certificate-jks.path=target/certs/grpc-tls-truststore.jks
quarkus.grpc.clients.hello.tls.trust-certificate-jks.password=****
----

If you use PEM certificates as trust-store, use the following configuration:

Check warning on line 164 in docs/src/main/asciidoc/grpc-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'.", "location": {"path": "docs/src/main/asciidoc/grpc-reference.adoc", "range": {"start": {"line": 164, "column": 29}}}, "severity": "INFO"}

[source,properties,subs=attributes+]
----
quarkus.grpc.clients.hello.plain-text=false # Use TLS
quarkus.grpc.clients.hello.use-quarkus-grpc-client=true # Use client using the Vert.x based transport
quarkus.grpc.clients.hello.tls.enabled=true
quarkus.grpc.clients.hello.tls.trust-certificate-pem.certs=target/certs/grpc-client-ca.crt
----

When using the gRPC Java client, you can configure TLS by setting the following properties in your `application.properties` file:

Check warning on line 174 in docs/src/main/asciidoc/grpc-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'.", "location": {"path": "docs/src/main/asciidoc/grpc-reference.adoc", "range": {"start": {"line": 174, "column": 5}}}, "severity": "INFO"}

[source,properties,subs=attributes+]
----
quarkus.grpc.clients.hello.ssl.trust-store=target/certs/grpc-client-tls-ca.crt
----

gRPC Java client only support the `PEM` format for the trust-store.

== Configuring mTLS

When using the Vert.x based server and Vert.x-based client, you can configure mTLS by setting the following properties in your `application.properties` file:

[source,properties,subs=attributes+]
----
# Server side:
quarkus.grpc.server.use-separate-server=false
quarkus.grpc.server.plain-text=false # Force the client to use TLS for the tests
quarkus.http.ssl.certificate.key-store-file=target/certs/grpc-keystore.jks
quarkus.http.ssl.certificate.key-store-password=****
quarkus.http.ssl.certificate.trust-store-file=target/certs/grpc-server-truststore.jks
quarkus.http.ssl.certificate.trust-store-password=****
quarkus.http.ssl.client-auth=REQUIRED # Force the client to authenticate, aka mTLS
quarkus.http.insecure-requests=disabled
# Client side:
quarkus.grpc.clients.hello.plain-text=false
quarkus.grpc.clients.hello.tls.trust-certificate-jks.path=target/certs/grpc-client-truststore.jks
quarkus.grpc.clients.hello.tls.trust-certificate-jks.password=****
quarkus.grpc.clients.hello.tls.key-certificate-jks.path=target/certs/grpc-client-keystore.jks
quarkus.grpc.clients.hello.tls.key-certificate-jks.password=****
quarkus.grpc.clients.hello.tls.enabled=true
quarkus.grpc.clients.hello.use-quarkus-grpc-client=true
----

If you use P12 format for the trust-store and the key-certificate, use the following configuration:

[source,properties,subs=attributes+]
----
# Server side
quarkus.grpc.server.use-separate-server=false
quarkus.grpc.server.plain-text=false # Force the client to use TLS for the tests
quarkus.http.ssl.certificate.key-store-file=target/certs/grpc-keystore.p12
quarkus.http.ssl.certificate.key-store-password=****
quarkus.http.ssl.certificate.trust-store-file=target/certs/grpc-server-truststore.p12
quarkus.http.ssl.certificate.trust-store-password=****
quarkus.http.ssl.client-auth=REQUIRED # Force the client to authenticate, aka mTLS
quarkus.http.insecure-requests=disabled
# Client side
quarkus.grpc.clients.hello.plain-text=false
quarkus.grpc.clients.hello.tls.trust-certificate-p12.path=target/certs/grpc-client-truststore.p12
quarkus.grpc.clients.hello.tls.trust-certificate-p12.password=****
quarkus.grpc.clients.hello.tls.key-certificate-p12.path=target/certs/grpc-client-keystore.p12
quarkus.grpc.clients.hello.tls.key-certificate-p12.password=****
quarkus.grpc.clients.hello.tls.enabled=true
quarkus.grpc.clients.hello.use-quarkus-grpc-client=true
----

1 change: 1 addition & 0 deletions docs/src/main/asciidoc/grpc.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ Quarkus gRPC is based on https://vertx.io/docs/vertx-grpc/java/[Vert.x gRPC].
* xref:grpc-kubernetes.adoc[Deploying your gRPC Service in Kubernetes]
* xref:grpc-xds.adoc[Enabling xDS gRPC support]
* xref:grpc-generation-reference.adoc[gRPC code generation reference guide]
* xref:grpc-reference.adoc[gRPC reference guide]
5 changes: 5 additions & 0 deletions extensions/grpc/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@
<artifactId>quarkus-elytron-security-properties-file-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>me.escoffier.certs</groupId>
<artifactId>certificate-generator-junit5</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@

import io.quarkus.grpc.client.tls.HelloWorldTlsEndpoint;
import io.quarkus.test.QuarkusUnitTest;
import me.escoffier.certs.Format;
import me.escoffier.certs.junit5.Certificate;
import me.escoffier.certs.junit5.Certificates;

@Certificates(baseDir = "target/certs", certificates = @Certificate(name = "grpc-client-tls", formats = Format.PEM))
class HelloWorldTlsEndpointTest {

@RegisterExtension
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package io.quarkus.grpc.client.tls;

import static org.assertj.core.api.Assertions.assertThat;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.grpc.examples.helloworld.GreeterGrpc;
import io.grpc.examples.helloworld.HelloReply;
import io.grpc.examples.helloworld.HelloRequest;
import io.quarkus.grpc.GrpcClient;
import io.quarkus.test.QuarkusUnitTest;
import me.escoffier.certs.Format;
import me.escoffier.certs.junit5.Certificate;
import me.escoffier.certs.junit5.Certificates;

@Certificates(baseDir = "target/certs", certificates = {
@Certificate(name = "grpc", password = "password", formats = { Format.JKS, Format.PEM, Format.PKCS12 }, client = true)
})
class MtlsWithJKSTrustStoreWithHttpServerTest {

private static final String configuration = """
quarkus.grpc.clients.hello.plain-text=false
quarkus.grpc.clients.hello.tls.trust-certificate-jks.path=target/certs/grpc-client-truststore.jks
quarkus.grpc.clients.hello.tls.trust-certificate-jks.password=password
quarkus.grpc.clients.hello.tls.key-certificate-jks.path=target/certs/grpc-client-keystore.jks
quarkus.grpc.clients.hello.tls.key-certificate-jks.password=password
quarkus.grpc.clients.hello.tls.enabled=true
quarkus.grpc.clients.hello.use-quarkus-grpc-client=true
quarkus.grpc.server.use-separate-server=false
quarkus.grpc.server.plain-text=false # Force the client to use TLS for the tests
quarkus.http.ssl.certificate.key-store-file=target/certs/grpc-keystore.jks
quarkus.http.ssl.certificate.key-store-password=password
quarkus.http.ssl.certificate.trust-store-file=target/certs/grpc-server-truststore.jks
quarkus.http.ssl.certificate.trust-store-password=password
quarkus.http.ssl.client-auth=REQUIRED
quarkus.http.insecure-requests=disabled
""";

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer(
() -> ShrinkWrap.create(JavaArchive.class)
.addPackage(HelloWorldTlsEndpoint.class.getPackage())
.addPackage(GreeterGrpc.class.getPackage())
.add(new StringAsset(configuration), "application.properties"));

@GrpcClient("hello")
GreeterGrpc.GreeterBlockingStub blockingHelloService;

@Test
void testClientTlsConfiguration() {
HelloReply reply = blockingHelloService.sayHello(HelloRequest.newBuilder().setName("neo").build());
assertThat(reply.getMessage()).isEqualTo("Hello neo");
}
}
Loading

0 comments on commit 44ba0d0

Please sign in to comment.