All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.quarkus.test.bootstrap.GrpcService Maven / Gradle / Ivy

package io.quarkus.test.bootstrap;

import java.io.File;
import java.util.Objects;

import javax.net.ssl.SSLException;

import io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.NettyChannelBuilder;
import io.netty.handler.ssl.ClientAuth;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.quarkus.test.security.certificate.CertificateBuilder;
import io.quarkus.test.security.certificate.PemClientCertificate;
import io.quarkus.test.services.URILike;
import io.quarkus.test.services.quarkus.model.QuarkusProperties;

public class GrpcService extends RestService {

    public CloseableManagedChannel grpcChannel() {
        var nettyChannel = NettyChannelBuilder.forAddress(getGrpcHost().getHost(), getGrpcHost().getPort()).usePlaintext()
                .build();
        return new CloseableManagedChannel(nettyChannel);
    }

    public CloseableManagedChannel securedGrpcChannel() {
        return securedGrpcChannel(null);
    }

    public CloseableManagedChannel securedGrpcChannel(String clientCnName) {
        if (QuarkusProperties.useSeparateGrpcServer(context)) {
            throw new IllegalStateException("TLS is not currently supported for a separate gRPC server");
        }
        var httpsUri = getURI(Protocol.HTTPS);
        var nettyChannel = NettyChannelBuilder.forAddress(httpsUri.getHost(), httpsUri.getPort())
                .sslContext(createSslContext(clientCnName))
                .useTransportSecurity()
                .build();
        return new CloseableManagedChannel(nettyChannel);
    }

    private SslContext createSslContext(String clientCnName) {
        SslContextBuilder sslContextBuilder = GrpcSslContexts.forClient();

        // only implementing this for one certificate and one client certificate for we don't need more ATM
        CertificateBuilder certBuilder = getPropertyFromContext(CertificateBuilder.INSTANCE_KEY);

        if (certBuilder.certificates().size() != 1) {
            throw new IllegalStateException(
                    """
                            Exactly one certificate must exist for the SSL configuration to work, found '%2$s'.
                            You can inspect existing certificates found in the '%1$s' yourself.
                            The '%1$s' is available in the ServiceContext under key '%3$s'.
                            """.formatted(CertificateBuilder.class.getName(), certBuilder.certificates().size(),
                            CertificateBuilder.INSTANCE_KEY));
        }
        var certificate = certBuilder.certificates().get(0);

        var clientCertificates = certificate.clientCertificates();
        if (clientCertificates.isEmpty()) {
            if (clientCnName != null) {
                throw new IllegalArgumentException("No client certificate found for CN: " + clientCnName);
            }
            sslContextBuilder.trustManager(new File(certificate.truststorePath()));
        } else {
            // mTLS scenario
            sslContextBuilder.clientAuth(ClientAuth.REQUIRE);

            final PemClientCertificate clientCert;
            if (clientCnName != null) {
                clientCert = (PemClientCertificate) certificate.getClientCertificateByCn(clientCnName);
                if (clientCert == null) {
                    throw new IllegalArgumentException("No client certificate found for CN: " + clientCnName);
                }
            } else {
                if (clientCertificates.size() > 1) {
                    throw new IllegalStateException("Only one client certificate is allowed");
                }
                if (clientCertificates.stream().findFirst().get() instanceof PemClientCertificate clientPemCert) {
                    clientCert = clientPemCert;
                } else {
                    throw new IllegalStateException("Only PEM format is supported for client certificates");
                }
            }
            sslContextBuilder.trustManager(new File(clientCert.truststorePath()));
            var certFile = new File(clientCert.certPath());
            var keyFile = new File(clientCert.keyPath());
            sslContextBuilder.keyManager(certFile, keyFile);
        }

        Objects.requireNonNull(certBuilder, """
                    Requested gRPC secured channel cannot be configured without the 'CertificateBuilder' in test context.
                    Use the '@Certificate' annotation to configure the 'CertificateBuilder'.
                """);
        try {
            return sslContextBuilder.build();
        } catch (SSLException e) {
            throw new RuntimeException(e);
        }
    }

    public URILike getGrpcHost() {
        return getURI(Protocol.GRPC);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy