diff --git a/docs/en/client/security.md b/docs/en/client/security.md index 7cde67029..756c5a9b8 100644 --- a/docs/en/client/security.md +++ b/docs/en/client/security.md @@ -87,6 +87,16 @@ If you use a service identifier, there may be problems with the certificate beca grpc.client.__name__.security.authorityOverride=localhost ```` +If you are using [Spring's SSL Bundles](https://docs.spring.io/spring-boot/reference/features/ssl.html), you can set +the `bundle` property instead like the following: + +````properties +spring.ssl.bundle.jks.__bundle_name__.truststore.location=file:certificates/trusted-servers.jks +#spring.ssl.bundle.jks.__bundle_name__.truststore.password=TruststorePassword +spring.ssl.bundle.jks.__bundle_name__.truststore.type=jks +grpc.client.__name__.security.bundle=__bundle_name__ +```` + ## Mutual Certificate Authentication In secure environments, you might have to authenticate yourself using a client certificate. This certificate is diff --git a/docs/en/server/security.md b/docs/en/server/security.md index 5ec85bbe3..de06d51b1 100644 --- a/docs/en/server/security.md +++ b/docs/en/server/security.md @@ -70,6 +70,18 @@ grpc.server.security.privateKey=file:certificates/server.key If you want to know what options are supported here, read [Spring's Resource docs](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#resources-resourceloader). +If you are using [Spring's SSL Bundles](https://docs.spring.io/spring-boot/reference/features/ssl.html), you can set +the `bundle` property instead like the following: + +````properties +spring.ssl.bundle.jks.server.key.alias=server +spring.ssl.bundle.jks.server.keystore.location=file:certificates/server.jks +#spring.ssl.bundle.jks.server.keystore.password=KeystorePassword +spring.ssl.bundle.jks.server.keystore.type=jks +grpc.server.security.enabled=true +grpc.server.security.bundle=server +```` + For the corresponding client configuration read the [Client Security](../client/security.md) page. ## Mutual Certificate Authentication diff --git a/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientAutoConfiguration.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientAutoConfiguration.java index 95e4a75f9..882c90b6c 100644 --- a/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientAutoConfiguration.java +++ b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientAutoConfiguration.java @@ -19,12 +19,14 @@ import java.util.Collections; import java.util.List; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.ssl.SslBundles; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -60,7 +62,8 @@ @Slf4j @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties -@AutoConfigureAfter(name = "org.springframework.cloud.client.CommonsClientAutoConfiguration", +@AutoConfigureAfter(name = {"org.springframework.cloud.client.CommonsClientAutoConfiguration", + "org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration"}, value = GrpcCommonCodecAutoConfiguration.class) public class GrpcClientAutoConfiguration { @@ -152,11 +155,12 @@ List defaultChannelConfigurers() { GrpcChannelFactory inProcessOrShadedNettyGrpcChannelFactory( final GrpcChannelsProperties properties, final GlobalClientInterceptorRegistry globalClientInterceptorRegistry, - final List channelConfigurers) { + final List channelConfigurers, + final ObjectProvider sslBundles) { log.info("Detected grpc-netty-shaded: Creating ShadedNettyChannelFactory + InProcessChannelFactory"); - final ShadedNettyChannelFactory channelFactory = - new ShadedNettyChannelFactory(properties, globalClientInterceptorRegistry, channelConfigurers); + final ShadedNettyChannelFactory channelFactory = new ShadedNettyChannelFactory(properties, + globalClientInterceptorRegistry, channelConfigurers, sslBundles.getIfAvailable()); final InProcessChannelFactory inProcessChannelFactory = new InProcessChannelFactory(properties, globalClientInterceptorRegistry, channelConfigurers); return new InProcessOrAlternativeChannelFactory(properties, inProcessChannelFactory, channelFactory); @@ -171,11 +175,13 @@ GrpcChannelFactory inProcessOrShadedNettyGrpcChannelFactory( GrpcChannelFactory inProcessOrNettyGrpcChannelFactory( final GrpcChannelsProperties properties, final GlobalClientInterceptorRegistry globalClientInterceptorRegistry, - final List channelConfigurers) { + final List channelConfigurers, + final ObjectProvider sslBundles) { log.info("Detected grpc-netty: Creating NettyChannelFactory + InProcessChannelFactory"); final NettyChannelFactory channelFactory = - new NettyChannelFactory(properties, globalClientInterceptorRegistry, channelConfigurers); + new NettyChannelFactory(properties, globalClientInterceptorRegistry, channelConfigurers, + sslBundles.getIfAvailable()); final InProcessChannelFactory inProcessChannelFactory = new InProcessChannelFactory(properties, globalClientInterceptorRegistry, channelConfigurers); return new InProcessOrAlternativeChannelFactory(properties, inProcessChannelFactory, channelFactory); @@ -190,10 +196,12 @@ GrpcChannelFactory inProcessOrNettyGrpcChannelFactory( GrpcChannelFactory shadedNettyGrpcChannelFactory( final GrpcChannelsProperties properties, final GlobalClientInterceptorRegistry globalClientInterceptorRegistry, - final List channelConfigurers) { + final List channelConfigurers, + final ObjectProvider sslBundles) { log.info("Detected grpc-netty-shaded: Creating ShadedNettyChannelFactory"); - return new ShadedNettyChannelFactory(properties, globalClientInterceptorRegistry, channelConfigurers); + return new ShadedNettyChannelFactory(properties, globalClientInterceptorRegistry, channelConfigurers, + sslBundles.getIfAvailable()); } // Then try the normal netty channel factory @@ -204,10 +212,12 @@ GrpcChannelFactory shadedNettyGrpcChannelFactory( GrpcChannelFactory nettyGrpcChannelFactory( final GrpcChannelsProperties properties, final GlobalClientInterceptorRegistry globalClientInterceptorRegistry, - final List channelConfigurers) { + final List channelConfigurers, + final ObjectProvider sslBundles) { log.info("Detected grpc-netty: Creating NettyChannelFactory"); - return new NettyChannelFactory(properties, globalClientInterceptorRegistry, channelConfigurers); + return new NettyChannelFactory(properties, globalClientInterceptorRegistry, channelConfigurers, + sslBundles.getIfAvailable()); } // Finally try the in process channel factory diff --git a/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/AbstractChannelFactory.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/AbstractChannelFactory.java index e16f79770..64b27ca6a 100644 --- a/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/AbstractChannelFactory.java +++ b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/AbstractChannelFactory.java @@ -32,6 +32,8 @@ import javax.annotation.concurrent.GuardedBy; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.lang.Nullable; import org.springframework.util.unit.DataSize; import com.google.common.collect.Lists; @@ -66,6 +68,8 @@ public abstract class AbstractChannelFactory> private final GrpcChannelsProperties properties; protected final GlobalClientInterceptorRegistry globalClientInterceptorRegistry; protected final List channelConfigurers; + @Nullable + protected final SslBundles sslBundles; /** * According to Thread safety in Grpc java * clients: {@link ManagedChannel}s should be reused to allow connection reuse. @@ -81,14 +85,17 @@ public abstract class AbstractChannelFactory> * @param properties The properties for the channels to create. * @param globalClientInterceptorRegistry The interceptor registry to use. * @param channelConfigurers The channel configurers to use. Can be empty. + * @param sslBundles Spring ssl configuration. Can be null if no bean configured. */ protected AbstractChannelFactory(final GrpcChannelsProperties properties, final GlobalClientInterceptorRegistry globalClientInterceptorRegistry, - final List channelConfigurers) { + final List channelConfigurers, + @Nullable final SslBundles sslBundles) { this.properties = requireNonNull(properties, "properties"); this.globalClientInterceptorRegistry = requireNonNull(globalClientInterceptorRegistry, "globalClientInterceptorRegistry"); this.channelConfigurers = requireNonNull(channelConfigurers, "channelConfigurers"); + this.sslBundles = sslBundles; } @Override @@ -216,7 +223,8 @@ protected void configureSecurity(final T builder, final String name) { || isNonNullAndNonBlank(security.getAuthorityOverride()) || security.getCertificateChain() != null || security.getPrivateKey() != null - || security.getTrustCertCollection() != null) { + || security.getTrustCertCollection() != null + || isNonNullAndNonBlank(security.getBundle())) { throw new IllegalStateException( "Security is configured but this implementation does not support security!"); } diff --git a/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/InProcessChannelFactory.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/InProcessChannelFactory.java index 3f8b054ef..99f2df615 100644 --- a/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/InProcessChannelFactory.java +++ b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/InProcessChannelFactory.java @@ -57,7 +57,7 @@ public InProcessChannelFactory(final GrpcChannelsProperties properties, public InProcessChannelFactory(final GrpcChannelsProperties properties, final GlobalClientInterceptorRegistry globalClientInterceptorRegistry, final List channelConfigurers) { - super(properties, globalClientInterceptorRegistry, channelConfigurers); + super(properties, globalClientInterceptorRegistry, channelConfigurers, null); } @Override diff --git a/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/NettyChannelFactory.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/NettyChannelFactory.java index ee4e54d57..a5dbcb9d0 100644 --- a/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/NettyChannelFactory.java +++ b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/NettyChannelFactory.java @@ -21,13 +21,17 @@ import java.io.InputStream; import java.net.URI; +import java.util.Arrays; import java.util.List; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLException; import javax.net.ssl.TrustManagerFactory; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; import org.springframework.core.io.Resource; +import org.springframework.lang.Nullable; import io.grpc.netty.GrpcSslContexts; import io.grpc.netty.NettyChannelBuilder; @@ -62,11 +66,13 @@ public class NettyChannelFactory extends AbstractChannelFactory channelConfigurers) { - super(properties, globalClientInterceptorRegistry, channelConfigurers); + final List channelConfigurers, + @Nullable final SslBundles sslBundles) { + super(properties, globalClientInterceptorRegistry, channelConfigurers, sslBundles); } @Override @@ -109,15 +115,29 @@ protected void configureSecurity(final NettyChannelBuilder builder, final String } final SslContextBuilder sslContextBuilder = GrpcSslContexts.forClient(); - configureProvidedClientCertificate(security, sslContextBuilder); - configureAcceptedServerCertificates(security, sslContextBuilder); + configureProvidedClientCertificate(security, sslContextBuilder, sslBundles); + configureAcceptedServerCertificates(security, sslContextBuilder, sslBundles); if (security.getCiphers() != null && !security.getCiphers().isEmpty()) { sslContextBuilder.ciphers(security.getCiphers()); + + } else if (sslBundles != null && security.getBundle() != null && !security.getBundle().isEmpty()) { + final SslBundle sslBundle = sslBundles.getBundle(security.getBundle()); + final String[] ciphers = sslBundle.getOptions().getCiphers(); + if (ciphers != null && ciphers.length != 0) { + sslContextBuilder.ciphers(Arrays.asList(ciphers)); + } } if (security.getProtocols() != null && security.getProtocols().length > 0) { sslContextBuilder.protocols(security.getProtocols()); + + } else if (sslBundles != null && security.getBundle() != null && !security.getBundle().isEmpty()) { + final SslBundle sslBundle = sslBundles.getBundle(security.getBundle()); + final String[] protocols = sslBundle.getOptions().getEnabledProtocols(); + if (protocols != null && protocols.length != 0) { + sslContextBuilder.protocols(protocols); + } } try { @@ -133,14 +153,17 @@ protected void configureSecurity(final NettyChannelBuilder builder, final String * * @param security The security configuration to use. * @param sslContextBuilder The ssl context builder to configure. + * @param sslBundles Spring ssl configuration. Can be null if no bean configured. */ // Keep this in sync with ShadedNettyChannelFactory#configureProvidedClientCertificate protected static void configureProvidedClientCertificate(final Security security, - final SslContextBuilder sslContextBuilder) { + final SslContextBuilder sslContextBuilder, + @Nullable final SslBundles sslBundles) { if (security.isClientAuthEnabled()) { try { final Resource privateKey = security.getPrivateKey(); final Resource keyStore = security.getKeyStore(); + final String bundleName = security.getBundle(); if (privateKey != null) { final Resource certificateChain = @@ -156,6 +179,11 @@ protected static void configureProvidedClientCertificate(final Security security security.getKeyStoreFormat(), keyStore, security.getKeyStorePassword()); sslContextBuilder.keyManager(keyManagerFactory); + } else if (sslBundles != null && bundleName != null && !bundleName.isEmpty()) { + SslBundle sslBundle = sslBundles.getBundle(bundleName); + final KeyManagerFactory keyManagerFactory = sslBundle.getManagers().getKeyManagerFactory(); + sslContextBuilder.keyManager(keyManagerFactory); + } else { throw new IllegalStateException("Neither privateKey nor keyStore configured"); } @@ -170,13 +198,16 @@ protected static void configureProvidedClientCertificate(final Security security * * @param security The security configuration to use. * @param sslContextBuilder The ssl context builder to configure. + * @param sslBundles Spring ssl configuration. Can be null if no bean configured. */ // Keep this in sync with ShadedNettyChannelFactory#configureAcceptedServerCertificates protected static void configureAcceptedServerCertificates(final Security security, - final SslContextBuilder sslContextBuilder) { + final SslContextBuilder sslContextBuilder, + @Nullable final SslBundles sslBundles) { try { final Resource trustCertCollection = security.getTrustCertCollection(); final Resource trustStore = security.getTrustStore(); + final String bundleName = security.getBundle(); if (trustCertCollection != null) { try (InputStream trustCertCollectionStream = trustCertCollection.getInputStream()) { @@ -188,6 +219,11 @@ protected static void configureAcceptedServerCertificates(final Security securit security.getTrustStoreFormat(), trustStore, security.getTrustStorePassword()); sslContextBuilder.trustManager(trustManagerFactory); + } else if (sslBundles != null && bundleName != null && !bundleName.isEmpty()) { + final SslBundle sslBundle = sslBundles.getBundle(bundleName); + final TrustManagerFactory trustManagerFactory = sslBundle.getManagers().getTrustManagerFactory(); + sslContextBuilder.trustManager(trustManagerFactory); + } else { // Use system default } diff --git a/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/ShadedNettyChannelFactory.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/ShadedNettyChannelFactory.java index 27eaf168a..f9c125f48 100644 --- a/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/ShadedNettyChannelFactory.java +++ b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/ShadedNettyChannelFactory.java @@ -21,13 +21,17 @@ import java.io.InputStream; import java.net.URI; +import java.util.Arrays; import java.util.List; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLException; import javax.net.ssl.TrustManagerFactory; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; import org.springframework.core.io.Resource; +import org.springframework.lang.Nullable; import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts; import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; @@ -61,11 +65,13 @@ public class ShadedNettyChannelFactory extends AbstractChannelFactory channelConfigurers) { - super(properties, globalClientInterceptorRegistry, channelConfigurers); + final List channelConfigurers, + @Nullable final SslBundles sslBundles) { + super(properties, globalClientInterceptorRegistry, channelConfigurers, sslBundles); } @Override @@ -109,15 +115,29 @@ protected void configureSecurity(final NettyChannelBuilder builder, final String } final SslContextBuilder sslContextBuilder = GrpcSslContexts.forClient(); - configureProvidedClientCertificate(security, sslContextBuilder); - configureAcceptedServerCertificates(security, sslContextBuilder); + configureProvidedClientCertificate(security, sslContextBuilder, sslBundles); + configureAcceptedServerCertificates(security, sslContextBuilder, sslBundles); if (security.getCiphers() != null && !security.getCiphers().isEmpty()) { sslContextBuilder.ciphers(security.getCiphers()); + + } else if (sslBundles != null && security.getBundle() != null && !security.getBundle().isEmpty()) { + final SslBundle sslBundle = sslBundles.getBundle(security.getBundle()); + final String[] ciphers = sslBundle.getOptions().getCiphers(); + if (ciphers != null && ciphers.length != 0) { + sslContextBuilder.ciphers(Arrays.asList(ciphers)); + } } if (security.getProtocols() != null && security.getProtocols().length > 0) { sslContextBuilder.protocols(security.getProtocols()); + } else if (sslBundles != null && security.getBundle() != null && !security.getBundle().isEmpty()) { + + final SslBundle sslBundle = sslBundles.getBundle(security.getBundle()); + final String[] protocols = sslBundle.getOptions().getEnabledProtocols(); + if (protocols != null && protocols.length != 0) { + sslContextBuilder.protocols(protocols); + } } try { @@ -133,14 +153,16 @@ protected void configureSecurity(final NettyChannelBuilder builder, final String * * @param security The security configuration to use. * @param sslContextBuilder The ssl context builder to configure. + * @param sslBundles Spring ssl configuration. Can be null if no bean configured. */ // Keep this in sync with NettyChannelFactory#configureProvidedClientCertificate protected static void configureProvidedClientCertificate(final Security security, - final SslContextBuilder sslContextBuilder) { + final SslContextBuilder sslContextBuilder, @Nullable final SslBundles sslBundles) { if (security.isClientAuthEnabled()) { try { final Resource privateKey = security.getPrivateKey(); final Resource keyStore = security.getKeyStore(); + final String bundleName = security.getBundle(); if (privateKey != null) { final Resource certificateChain = @@ -156,6 +178,11 @@ protected static void configureProvidedClientCertificate(final Security security security.getKeyStoreFormat(), keyStore, security.getKeyStorePassword()); sslContextBuilder.keyManager(keyManagerFactory); + } else if (sslBundles != null && bundleName != null && !bundleName.isEmpty()) { + final SslBundle sslBundle = sslBundles.getBundle(bundleName); + final KeyManagerFactory keyManagerFactory = sslBundle.getManagers().getKeyManagerFactory(); + sslContextBuilder.keyManager(keyManagerFactory); + } else { throw new IllegalStateException("Neither privateKey nor keyStore configured"); } @@ -170,13 +197,16 @@ protected static void configureProvidedClientCertificate(final Security security * * @param security The security configuration to use. * @param sslContextBuilder The ssl context builder to configure. + * @param sslBundles Spring ssl configuration. Can be null if no bean configured. */ // Keep this in sync with NettyChannelFactory#configureAcceptedServerCertificates protected static void configureAcceptedServerCertificates(final Security security, - final SslContextBuilder sslContextBuilder) { + final SslContextBuilder sslContextBuilder, + @Nullable final SslBundles sslBundles) { try { final Resource trustCertCollection = security.getTrustCertCollection(); final Resource trustStore = security.getTrustStore(); + final String bundleName = security.getBundle(); if (trustCertCollection != null) { try (InputStream trustCertCollectionStream = trustCertCollection.getInputStream()) { @@ -188,6 +218,11 @@ protected static void configureAcceptedServerCertificates(final Security securit security.getTrustStoreFormat(), trustStore, security.getTrustStorePassword()); sslContextBuilder.trustManager(trustManagerFactory); + } else if (sslBundles != null && bundleName != null && !bundleName.isEmpty()) { + final SslBundle sslBundle = sslBundles.getBundle(bundleName); + final TrustManagerFactory trustManagerFactory = sslBundle.getManagers().getTrustManagerFactory(); + sslContextBuilder.trustManager(trustManagerFactory); + } else { // Use system default } diff --git a/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/config/GrpcChannelProperties.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/config/GrpcChannelProperties.java index 301a89e73..086803924 100644 --- a/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/config/GrpcChannelProperties.java +++ b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/config/GrpcChannelProperties.java @@ -866,6 +866,18 @@ public void setTrustStorePassword(final String trustStorePassword) { // -------------------------------------------------- + private String bundle; + + public String getBundle() { + return this.bundle; + } + + public void setBundle(String bundle) { + this.bundle = bundle; + } + + // -------------------------------------------------- + private String authorityOverride = null; /** @@ -994,6 +1006,9 @@ public void copyDefaultsFrom(final Security config) { if (this.trustStorePassword == null) { this.trustStorePassword = config.trustStorePassword; } + if (this.bundle == null) { + this.bundle = config.bundle; + } if (this.authorityOverride == null) { this.authorityOverride = config.authorityOverride; } diff --git a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerFactoryAutoConfiguration.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerFactoryAutoConfiguration.java index f14e7ab95..b51e21824 100644 --- a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerFactoryAutoConfiguration.java +++ b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerFactoryAutoConfiguration.java @@ -18,11 +18,13 @@ import java.util.List; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.ssl.SslBundles; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; @@ -59,6 +61,7 @@ public class GrpcServerFactoryAutoConfiguration { * @param properties The properties used to configure the server. * @param serviceDiscoverer The discoverer used to identify the services that should be served. * @param serverConfigurers The server configurers that contain additional configuration for the server. + * @param sslBundles Spring ssl configuration. Can be null if no bean configured. * @return The shadedNettyGrpcServerFactory bean. */ @ConditionalOnClass(name = {"io.grpc.netty.shaded.io.netty.channel.Channel", @@ -68,10 +71,12 @@ public class GrpcServerFactoryAutoConfiguration { public ShadedNettyGrpcServerFactory shadedNettyGrpcServerFactory( final GrpcServerProperties properties, final GrpcServiceDiscoverer serviceDiscoverer, - final List serverConfigurers) { + final List serverConfigurers, + final ObjectProvider sslBundles) { log.info("Detected grpc-netty-shaded: Creating ShadedNettyGrpcServerFactory"); - final ShadedNettyGrpcServerFactory factory = new ShadedNettyGrpcServerFactory(properties, serverConfigurers); + final ShadedNettyGrpcServerFactory factory = + new ShadedNettyGrpcServerFactory(properties, serverConfigurers, sslBundles.getIfAvailable()); for (final GrpcServiceDefinition service : serviceDiscoverer.findGrpcServices()) { factory.addService(service); } @@ -103,6 +108,7 @@ public GrpcServerLifecycle shadedNettyGrpcServerLifecycle( * @param properties The properties used to configure the server. * @param serviceDiscoverer The discoverer used to identify the services that should be served. * @param serverConfigurers The server configurers that contain additional configuration for the server. + * @param sslBundles Spring ssl configuration. Can be null if no bean configured. * @return The shadedNettyGrpcServerFactory bean. */ @ConditionalOnMissingBean(ShadedNettyGrpcServerFactory.class) @@ -112,10 +118,12 @@ public GrpcServerLifecycle shadedNettyGrpcServerLifecycle( public NettyGrpcServerFactory nettyGrpcServerFactory( final GrpcServerProperties properties, final GrpcServiceDiscoverer serviceDiscoverer, - final List serverConfigurers) { + final List serverConfigurers, + final ObjectProvider sslBundles) { log.info("Detected grpc-netty: Creating NettyGrpcServerFactory"); - final NettyGrpcServerFactory factory = new NettyGrpcServerFactory(properties, serverConfigurers); + final NettyGrpcServerFactory factory = new NettyGrpcServerFactory(properties, serverConfigurers, + sslBundles.getIfAvailable()); for (final GrpcServiceDefinition service : serviceDiscoverer.findGrpcServices()) { factory.addService(service); } @@ -155,7 +163,7 @@ public InProcessGrpcServerFactory inProcessGrpcServerFactory( final List serverConfigurers) { log.info("'grpc.server.in-process-name' is set: Creating InProcessGrpcServerFactory"); - final InProcessGrpcServerFactory factory = new InProcessGrpcServerFactory(properties, serverConfigurers); + final InProcessGrpcServerFactory factory = new InProcessGrpcServerFactory(properties); for (final GrpcServiceDefinition service : serviceDiscoverer.findGrpcServices()) { factory.addService(service); } diff --git a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/config/GrpcServerProperties.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/config/GrpcServerProperties.java index cb4605d03..8fe0d1da8 100644 --- a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/config/GrpcServerProperties.java +++ b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/config/GrpcServerProperties.java @@ -397,6 +397,8 @@ public static class Security { */ private String trustStorePassword = null; + private String bundle = null; + /** * Specifies the cipher suite. If {@code null} or empty it will use the system's default cipher suite. * diff --git a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/AbstractGrpcServerFactory.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/AbstractGrpcServerFactory.java index 8401f6513..1fb0ef30d 100644 --- a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/AbstractGrpcServerFactory.java +++ b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/AbstractGrpcServerFactory.java @@ -22,6 +22,8 @@ import java.util.List; import java.util.Set; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.lang.Nullable; import org.springframework.util.unit.DataSize; import com.google.common.collect.Lists; @@ -46,6 +48,8 @@ public abstract class AbstractGrpcServerFactory> impl private final List serviceList = Lists.newLinkedList(); protected final GrpcServerProperties properties; + @Nullable + protected final SslBundles sslBundles; protected final List serverConfigurers; /** @@ -53,10 +57,13 @@ public abstract class AbstractGrpcServerFactory> impl * * @param properties The properties used to configure the server. * @param serverConfigurers The server configurers to use. Can be empty. + * @param sslBundles Spring ssl configuration. Can be null if no bean configured. */ protected AbstractGrpcServerFactory(final GrpcServerProperties properties, - final List serverConfigurers) { + final List serverConfigurers, + @Nullable final SslBundles sslBundles) { this.properties = requireNonNull(properties, "properties"); + this.sslBundles = sslBundles; this.serverConfigurers = requireNonNull(serverConfigurers, "serverConfigurers"); } diff --git a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/InProcessGrpcServerFactory.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/InProcessGrpcServerFactory.java index 1c77927d3..93e855248 100644 --- a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/InProcessGrpcServerFactory.java +++ b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/InProcessGrpcServerFactory.java @@ -72,7 +72,7 @@ public InProcessGrpcServerFactory(final String name, final GrpcServerProperties */ public InProcessGrpcServerFactory(final String name, final GrpcServerProperties properties, final List serverConfigurers) { - super(properties, serverConfigurers); + super(properties, serverConfigurers, null); this.name = requireNonNull(name, "name"); } diff --git a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/NettyGrpcServerFactory.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/NettyGrpcServerFactory.java index 3a9b6f6a1..92891c3ac 100644 --- a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/NettyGrpcServerFactory.java +++ b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/NettyGrpcServerFactory.java @@ -22,6 +22,7 @@ import java.io.InputStream; import java.net.InetSocketAddress; +import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; @@ -29,7 +30,10 @@ import javax.net.ssl.SSLException; import javax.net.ssl.TrustManagerFactory; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; import org.springframework.core.io.Resource; +import org.springframework.lang.Nullable; import com.google.common.net.InetAddresses; @@ -57,10 +61,12 @@ public class NettyGrpcServerFactory extends AbstractGrpcServerFactory serverConfigurers) { - super(properties, serverConfigurers); + final List serverConfigurers, + @Nullable final SslBundles sslBundles) { + super(properties, serverConfigurers, sslBundles); } @Override @@ -112,18 +118,32 @@ protected void configureSecurity(final NettyServerBuilder builder) { final Security security = this.properties.getSecurity(); if (security.isEnabled()) { // Provided server certificates - final SslContextBuilder sslContextBuilder = newServerSslContextBuilder(security); + final SslContextBuilder sslContextBuilder = newServerSslContextBuilder(security, sslBundles); // Accepted client certificates - configureAcceptedClientCertificates(security, sslContextBuilder); + configureAcceptedClientCertificates(security, sslContextBuilder, sslBundles); // Other configuration if (security.getCiphers() != null && !security.getCiphers().isEmpty()) { sslContextBuilder.ciphers(security.getCiphers()); + + } else if (sslBundles != null && security.getBundle() != null && !security.getBundle().isEmpty()) { + final SslBundle sslBundle = sslBundles.getBundle(security.getBundle()); + final String[] ciphers = sslBundle.getOptions().getCiphers(); + if (ciphers != null && ciphers.length != 0) { + sslContextBuilder.ciphers(Arrays.asList(ciphers)); + } } if (security.getProtocols() != null && security.getProtocols().length > 0) { sslContextBuilder.protocols(security.getProtocols()); + + } else if (sslBundles != null && security.getBundle() != null && !security.getBundle().isEmpty()) { + final SslBundle sslBundle = sslBundles.getBundle(security.getBundle()); + final String[] protocols = sslBundle.getOptions().getEnabledProtocols(); + if (protocols != null && protocols.length != 0) { + sslContextBuilder.protocols(protocols); + } } try { @@ -141,10 +161,12 @@ protected void configureSecurity(final NettyServerBuilder builder) { * @return The newly created SslContextBuilder. */ // Keep this in sync with ShadedNettyGrpcServerFactory#newServerSslContextBuilder - protected static SslContextBuilder newServerSslContextBuilder(final Security security) { + protected static SslContextBuilder newServerSslContextBuilder(final Security security, + @Nullable final SslBundles sslBundles) { try { final Resource privateKey = security.getPrivateKey(); final Resource keyStore = security.getKeyStore(); + final String bundleName = security.getBundle(); if (privateKey != null) { final Resource certificateChain = @@ -160,6 +182,11 @@ protected static SslContextBuilder newServerSslContextBuilder(final Security sec security.getKeyStoreFormat(), keyStore, security.getKeyStorePassword()); return GrpcSslContexts.configure(SslContextBuilder.forServer(keyManagerFactory)); + } else if (sslBundles != null && bundleName != null && !bundleName.isEmpty()) { + final SslBundle sslBundle = sslBundles.getBundle(bundleName); + final KeyManagerFactory keyManagerFactory = sslBundle.getManagers().getKeyManagerFactory(); + return GrpcSslContexts.configure(SslContextBuilder.forServer(keyManagerFactory)); + } else { throw new IllegalStateException("Neither privateKey nor keyStore configured"); } @@ -177,7 +204,8 @@ protected static SslContextBuilder newServerSslContextBuilder(final Security sec // Keep this in sync with ShadedNettyGrpcServerFactory#configureAcceptedClientCertificates protected static void configureAcceptedClientCertificates( final Security security, - final SslContextBuilder sslContextBuilder) { + final SslContextBuilder sslContextBuilder, + @Nullable final SslBundles sslBundles) { if (security.getClientAuth() != ClientAuth.NONE) { sslContextBuilder.clientAuth(of(security.getClientAuth())); @@ -185,6 +213,7 @@ protected static void configureAcceptedClientCertificates( try { final Resource trustCertCollection = security.getTrustCertCollection(); final Resource trustStore = security.getTrustStore(); + final String bundleName = security.getBundle(); if (trustCertCollection != null) { try (InputStream trustCertCollectionStream = trustCertCollection.getInputStream()) { @@ -196,6 +225,10 @@ protected static void configureAcceptedClientCertificates( security.getTrustStoreFormat(), trustStore, security.getTrustStorePassword()); sslContextBuilder.trustManager(trustManagerFactory); + } else if (sslBundles != null && bundleName != null && !bundleName.isEmpty()) { + final SslBundle sslBundle = sslBundles.getBundle(bundleName); + sslContextBuilder.trustManager(sslBundle.getManagers().getTrustManagerFactory()); + } else { // Use system default } diff --git a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/ShadedNettyGrpcServerFactory.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/ShadedNettyGrpcServerFactory.java index f523a99ea..93d1e32cf 100644 --- a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/ShadedNettyGrpcServerFactory.java +++ b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/ShadedNettyGrpcServerFactory.java @@ -22,6 +22,7 @@ import java.io.InputStream; import java.net.InetSocketAddress; +import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; @@ -29,7 +30,10 @@ import javax.net.ssl.SSLException; import javax.net.ssl.TrustManagerFactory; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; import org.springframework.core.io.Resource; +import org.springframework.lang.Nullable; import com.google.common.net.InetAddresses; @@ -58,10 +62,12 @@ public class ShadedNettyGrpcServerFactory * * @param properties The properties used to configure the server. * @param serverConfigurers The server configurers to use. Can be empty. + * @param sslBundles Spring ssl bundles for when security is configured via SslBundles */ public ShadedNettyGrpcServerFactory(final GrpcServerProperties properties, - final List serverConfigurers) { - super(properties, serverConfigurers); + final List serverConfigurers, + @Nullable final SslBundles sslBundles) { + super(properties, serverConfigurers, sslBundles); } @Override @@ -112,18 +118,30 @@ protected void configureSecurity(final NettyServerBuilder builder) { final Security security = this.properties.getSecurity(); if (security.isEnabled()) { // Provided server certificates - final SslContextBuilder sslContextBuilder = newServerSslContextBuilder(security); + final SslContextBuilder sslContextBuilder = newServerSslContextBuilder(security, sslBundles); // Accepted client certificates - configureAcceptedClientCertificates(security, sslContextBuilder); + configureAcceptedClientCertificates(security, sslContextBuilder, sslBundles); // Other configuration if (security.getCiphers() != null && !security.getCiphers().isEmpty()) { sslContextBuilder.ciphers(security.getCiphers()); + } else if (sslBundles != null && security.getBundle() != null && !security.getBundle().isEmpty()) { + final SslBundle sslBundle = sslBundles.getBundle(security.getBundle()); + final String[] ciphers = sslBundle.getOptions().getCiphers(); + if (ciphers != null && ciphers.length != 0) { + sslContextBuilder.ciphers(Arrays.asList(ciphers)); + } } if (security.getProtocols() != null && security.getProtocols().length > 0) { sslContextBuilder.protocols(security.getProtocols()); + } else if (sslBundles != null && security.getBundle() != null && !security.getBundle().isEmpty()) { + final SslBundle sslBundle = sslBundles.getBundle(security.getBundle()); + final String[] protocols = sslBundle.getOptions().getEnabledProtocols(); + if (protocols != null && protocols.length != 0) { + sslContextBuilder.protocols(protocols); + } } try { @@ -141,11 +159,12 @@ protected void configureSecurity(final NettyServerBuilder builder) { * @return The newly created SslContextBuilder. */ // Keep this in sync with NettyGrpcServerFactory#newServerSslContextBuilder - protected static SslContextBuilder newServerSslContextBuilder(final Security security) { + protected static SslContextBuilder newServerSslContextBuilder(final Security security, + @Nullable final SslBundles sslBundles) { try { final Resource privateKey = security.getPrivateKey(); final Resource keyStore = security.getKeyStore(); - + final String bundleName = security.getBundle(); if (privateKey != null) { final Resource certificateChain = requireNonNull(security.getCertificateChain(), "certificateChain"); @@ -160,6 +179,11 @@ protected static SslContextBuilder newServerSslContextBuilder(final Security sec security.getKeyStoreFormat(), keyStore, security.getKeyStorePassword()); return GrpcSslContexts.configure(SslContextBuilder.forServer(keyManagerFactory)); + } else if (sslBundles != null && bundleName != null && !bundleName.isEmpty()) { + final SslBundle sslBundle = sslBundles.getBundle(bundleName); + final KeyManagerFactory keyManagerFactory = sslBundle.getManagers().getKeyManagerFactory(); + return GrpcSslContexts.configure(SslContextBuilder.forServer(keyManagerFactory)); + } else { throw new IllegalStateException("Neither privateKey nor keyStore configured"); } @@ -177,7 +201,8 @@ protected static SslContextBuilder newServerSslContextBuilder(final Security sec // Keep this in sync with NettyGrpcServerFactory#configureAcceptedClientCertificates protected static void configureAcceptedClientCertificates( final Security security, - final SslContextBuilder sslContextBuilder) { + final SslContextBuilder sslContextBuilder, + @Nullable final SslBundles sslBundles) { if (security.getClientAuth() != ClientAuth.NONE) { sslContextBuilder.clientAuth(of(security.getClientAuth())); @@ -185,6 +210,7 @@ protected static void configureAcceptedClientCertificates( try { final Resource trustCertCollection = security.getTrustCertCollection(); final Resource trustStore = security.getTrustStore(); + final String bundleName = security.getBundle(); if (trustCertCollection != null) { try (InputStream trustCertCollectionStream = trustCertCollection.getInputStream()) { @@ -196,6 +222,10 @@ protected static void configureAcceptedClientCertificates( security.getTrustStoreFormat(), trustStore, security.getTrustStorePassword()); sslContextBuilder.trustManager(trustManagerFactory); + } else if (sslBundles != null && bundleName != null && !bundleName.isEmpty()) { + final SslBundle sslBundle = sslBundles.getBundle(bundleName); + sslContextBuilder.trustManager(sslBundle.getManagers().getTrustManagerFactory()); + } else { // Use system default } diff --git a/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/serverfactory/AbstractGrpcServerFactoryTest.java b/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/serverfactory/AbstractGrpcServerFactoryTest.java index 881a7980e..298e6256e 100644 --- a/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/serverfactory/AbstractGrpcServerFactoryTest.java +++ b/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/serverfactory/AbstractGrpcServerFactoryTest.java @@ -42,7 +42,7 @@ void testConfigureServices() { final GrpcServerProperties properties = new GrpcServerProperties(); properties.setReflectionServiceEnabled(false); - final NettyGrpcServerFactory serverFactory = new NettyGrpcServerFactory(properties, emptyList()); + final NettyGrpcServerFactory serverFactory = new NettyGrpcServerFactory(properties, emptyList(), null); serverFactory.addService(new GrpcServiceDefinition("test1", ProtoReflectionService.class, ProtoReflectionService.newInstance().bindService())); diff --git a/tests/src/test/java/net/devh/boot/grpc/test/setup/CustomCiphersAndProtocolsBundleSetupTest.java b/tests/src/test/java/net/devh/boot/grpc/test/setup/CustomCiphersAndProtocolsBundleSetupTest.java new file mode 100644 index 000000000..2068a2a7b --- /dev/null +++ b/tests/src/test/java/net/devh/boot/grpc/test/setup/CustomCiphersAndProtocolsBundleSetupTest.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2016-2023 The gRPC-Spring 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 net.devh.boot.grpc.test.setup; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import javax.net.ssl.SSLHandshakeException; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import com.google.protobuf.Empty; + +import io.grpc.StatusRuntimeException; +import net.devh.boot.grpc.client.inject.GrpcClient; +import net.devh.boot.grpc.test.config.BaseAutoConfiguration; +import net.devh.boot.grpc.test.config.ServiceConfiguration; +import net.devh.boot.grpc.test.proto.TestServiceGrpc; + +@SpringBootTest(properties = { + "grpc.server.security.enabled=true", + "grpc.server.security.bundle=server", + "spring.ssl.bundle.pem.server.keystore.certificate=file:src/test/resources/certificates/server.crt", + "spring.ssl.bundle.pem.server.keystore.privateKey=file:src/test/resources/certificates/server.key", + "spring.ssl.bundle.pem.server.options.ciphers=TLS_AES_256_GCM_SHA384,ECDHE-RSA-AES256-GCM-SHA384", + "spring.ssl.bundle.pem.server.options.enabledProtocols=TLSv1.3,TLSv1.2", + + "grpc.client.GLOBAL.address=localhost:9090", + "grpc.client.GLOBAL.security.authorityOverride=localhost", + "grpc.client.GLOBAL.security.trustCertCollection=file:src/test/resources/certificates/trusted-servers-collection", + "grpc.client.GLOBAL.negotiationType=TLS", + + "spring.ssl.bundle.pem.tls11.options.enabledProtocols=TLSv1.1", + "spring.ssl.bundle.pem.tls11.options.ciphers=ECDHE-RSA-AES256-SHA", + "grpc.client.tls11.security.bundle=tls11", + + "spring.ssl.bundle.pem.tls12.options.enabledProtocols=TLSv1.2", + "spring.ssl.bundle.pem.tls12.options.ciphers=ECDHE-RSA-AES256-GCM-SHA384", + "grpc.client.tls12.security.bundle=tls12", + + "spring.ssl.bundle.pem.tls13.options.enabledProtocols=TLSv1.3", + "spring.ssl.bundle.pem.tls13.options.ciphers=TLS_AES_256_GCM_SHA384", + "grpc.client.tls13.security.bundle=tls13", + + "spring.ssl.bundle.pem.noSharedCiphers.options.enabledProtocols=TLSv1.2,TLSv1.1", + "spring.ssl.bundle.pem.noSharedCiphers.options.ciphers=ECDHE-RSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-SHA", + "grpc.client.noSharedCiphers.security.bundle=noSharedCiphers", + + "spring.ssl.bundle.pem.noSharedProtocols.options.enabledProtocols=TLSv1.1", + "spring.ssl.bundle.pem.noSharedProtocols.options.ciphers=ECDHE-RSA-AES128-SHA", + "grpc.client.noSharedProtocols.security.bundle=noSharedProtocols", +}) +@SpringJUnitConfig(classes = {ServiceConfiguration.class, BaseAutoConfiguration.class, SslAutoConfiguration.class}) +@DirtiesContext +class CustomCiphersAndProtocolsBundleSetupTest extends AbstractSimpleServerClientTest { + + @GrpcClient("test") + private TestServiceGrpc.TestServiceBlockingStub test; + @GrpcClient("tls11") + private TestServiceGrpc.TestServiceBlockingStub tlsV11Stub; + @GrpcClient("tls12") + private TestServiceGrpc.TestServiceBlockingStub tlsV12Stub; + @GrpcClient("tls13") + private TestServiceGrpc.TestServiceBlockingStub tlsV13Stub; + @GrpcClient("noSharedCiphers") + private TestServiceGrpc.TestServiceBlockingStub tlsNoSharedCiphersStub; + @GrpcClient("noSharedProtocols") + private TestServiceGrpc.TestServiceBlockingStub tlsNoSharedProtocolsStub; + + /** + * Tests behaviour with TLSv1.1 and shared protocols. Test should fail, as the server does not support TLSv1.1. + */ + @Test + public void testTlsV11Stub() { + + Exception exception = assertThrows(StatusRuntimeException.class, () -> { + tlsV11Stub.normal(Empty.getDefaultInstance()).getVersion(); + }); + assertTrue(exception.getCause() instanceof SSLHandshakeException); + } + + /** + * Tests behaviour with TLSv1.2 and shared protocols. Test should succeed, as the server supports TLSv1.2. + */ + @Test + public void testTlsV12Stub() { + + assertEquals("1.2.3", + tlsV12Stub.normal(Empty.getDefaultInstance()).getVersion()); + } + + /** + * Tests behaviour with TLSv1.3 and shared protocols. Test should succeed, as the server supports TLSv1.3. + */ + @Test + public void testTlsV13Stub() { + + assertEquals("1.2.3", + tlsV13Stub.normal(Empty.getDefaultInstance()).getVersion()); + } + + /** + * Tests behaviour with no shared ciphers. Test should fail with a {@link SSLHandshakeException} + */ + @Test + public void testNoSharedCiphersClientStub() { + + Exception exception = assertThrows(StatusRuntimeException.class, () -> { + tlsNoSharedCiphersStub.normal(Empty.getDefaultInstance()).getVersion(); + }); + assertTrue(exception.getCause() instanceof SSLHandshakeException); + } + + /** + * Tests behaviour with no shared protocols. Test should fail with a {@link SSLHandshakeException} as the server + * does not support TLSv1.1. + */ + @Test + public void testNoSharedProtocolsStub() { + + Exception exception = assertThrows(StatusRuntimeException.class, () -> { + tlsNoSharedProtocolsStub.normal(Empty.getDefaultInstance()).getVersion(); + }); + assertTrue(exception.getCause() instanceof SSLHandshakeException); + } +} diff --git a/tests/src/test/java/net/devh/boot/grpc/test/setup/SelfSignedMutualJksBundleSetupTest.java b/tests/src/test/java/net/devh/boot/grpc/test/setup/SelfSignedMutualJksBundleSetupTest.java new file mode 100644 index 000000000..67dd488a8 --- /dev/null +++ b/tests/src/test/java/net/devh/boot/grpc/test/setup/SelfSignedMutualJksBundleSetupTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2016-2023 The gRPC-Spring 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 net.devh.boot.grpc.test.setup; + +import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import lombok.extern.slf4j.Slf4j; +import net.devh.boot.grpc.test.config.BaseAutoConfiguration; +import net.devh.boot.grpc.test.config.ServiceConfiguration; + +/** + * A test checking that the server and client can start and connect to each other with minimal config. + * + * @author Daniel Theuke (daniel.theuke@heuboe.de) + */ +@Slf4j +@SpringBootTest(properties = { + "spring.ssl.bundle.jks.grpc_server.key.alias=server", + "spring.ssl.bundle.jks.grpc_server.keystore.location=file:src/test/resources/certificates/server.jks", + "spring.ssl.bundle.jks.grpc_server.keystore.password=", + "spring.ssl.bundle.jks.grpc_server.truststore.location=file:src/test/resources/certificates/trusted-clients.jks", + "spring.ssl.bundle.jks.grpc_server.truststore.password=", + + "grpc.server.security.enabled=true", + "grpc.server.security.bundle=grpc_server", + "grpc.server.security.clientAuth=REQUIRE", + + "spring.ssl.bundle.jks.grpc_client.key.alias=client1", + "spring.ssl.bundle.jks.grpc_client.keystore.location=file:src/test/resources/certificates/client1.jks", + "spring.ssl.bundle.jks.grpc_client.keystore.password=", + "spring.ssl.bundle.jks.grpc_client.truststore.location=file:src/test/resources/certificates/trusted-servers.jks", + "spring.ssl.bundle.jks.grpc_client.truststore.password=", + + "grpc.client.test.address=localhost:9090", + "grpc.client.test.security.authorityOverride=localhost", + "grpc.client.test.security.bundle=grpc_client", + "grpc.client.test.security.clientAuthEnabled=true", +}) +@SpringJUnitConfig(classes = {ServiceConfiguration.class, BaseAutoConfiguration.class, SslAutoConfiguration.class}) +@DirtiesContext +public class SelfSignedMutualJksBundleSetupTest extends AbstractSimpleServerClientTest { + + public SelfSignedMutualJksBundleSetupTest() { + log.info("--- SelfSignedMutualJksKeystoreSetupTest ---"); + } + +} diff --git a/tests/src/test/java/net/devh/boot/grpc/test/setup/SelfSignedMutualP12BundleSetupTest.java b/tests/src/test/java/net/devh/boot/grpc/test/setup/SelfSignedMutualP12BundleSetupTest.java new file mode 100644 index 000000000..334d7750b --- /dev/null +++ b/tests/src/test/java/net/devh/boot/grpc/test/setup/SelfSignedMutualP12BundleSetupTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2016-2023 The gRPC-Spring 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 net.devh.boot.grpc.test.setup; + +import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import lombok.extern.slf4j.Slf4j; +import net.devh.boot.grpc.test.config.BaseAutoConfiguration; +import net.devh.boot.grpc.test.config.ServiceConfiguration; + +/** + * A test checking that the server and client can start and connect to each other with minimal config. + * + * @author Daniel Theuke (daniel.theuke@heuboe.de) + */ +@Slf4j +@SpringBootTest(properties = { + "spring.ssl.bundle.jks.grpc_server.key.alias=server", + "spring.ssl.bundle.jks.grpc_server.keystore.location=file:src/test/resources/certificates/server.p12", + "spring.ssl.bundle.jks.grpc_server.keystore.password=", + "spring.ssl.bundle.jks.grpc_server.truststore.location=file:src/test/resources/certificates/trusted-clients.p12", + "spring.ssl.bundle.jks.grpc_server.truststore.password=", + + "grpc.server.security.enabled=true", + "grpc.server.security.bundle=grpc_server", + "grpc.server.security.clientAuth=REQUIRE", + + "spring.ssl.bundle.jks.grpc_client.key.alias=client1", + "spring.ssl.bundle.jks.grpc_client.keystore.location=file:src/test/resources/certificates/client1.p12", + "spring.ssl.bundle.jks.grpc_client.keystore.password=", + "spring.ssl.bundle.jks.grpc_client.truststore.location=file:src/test/resources/certificates/trusted-servers.p12", + "spring.ssl.bundle.jks.grpc_client.truststore.password=", + + "grpc.client.test.address=localhost:9090", + "grpc.client.test.security.authorityOverride=localhost", + "grpc.client.test.security.bundle=grpc_client", + "grpc.client.test.security.clientAuthEnabled=true", +}) +@SpringJUnitConfig(classes = {ServiceConfiguration.class, BaseAutoConfiguration.class, SslAutoConfiguration.class}) +@DirtiesContext +public class SelfSignedMutualP12BundleSetupTest extends AbstractSimpleServerClientTest { + + public SelfSignedMutualP12BundleSetupTest() { + log.info("--- SelfSignedMutualJksKeystoreSetupTest ---"); + } + +} diff --git a/tests/src/test/java/net/devh/boot/grpc/test/setup/SelfSignedServerBundleSetupTest.java b/tests/src/test/java/net/devh/boot/grpc/test/setup/SelfSignedServerBundleSetupTest.java new file mode 100644 index 000000000..49ff5528f --- /dev/null +++ b/tests/src/test/java/net/devh/boot/grpc/test/setup/SelfSignedServerBundleSetupTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2016-2023 The gRPC-Spring 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 net.devh.boot.grpc.test.setup; + +import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import lombok.extern.slf4j.Slf4j; +import net.devh.boot.grpc.test.config.BaseAutoConfiguration; +import net.devh.boot.grpc.test.config.ServiceConfiguration; + +/** + * A test checking that the server and client can start and connect to each other with minimal config. + * + * @author Daniel Theuke (daniel.theuke@heuboe.de) + */ +@Slf4j +@SpringBootTest(properties = { + "spring.ssl.bundle.jks.grpc_server.key.alias=server", + "spring.ssl.bundle.jks.grpc_server.keystore.location=file:src/test/resources/certificates/server.p12", + "spring.ssl.bundle.jks.grpc_server.keystore.password=", + "spring.ssl.bundle.jks.grpc_client.truststore.location=file:src/test/resources/certificates/trusted-servers.p12", + "spring.ssl.bundle.jks.grpc_client.truststore.password=", + "grpc.server.security.enabled=true", + "grpc.server.security.bundle=grpc_server", + "grpc.client.test.address=localhost:9090", + "grpc.client.test.security.enabled=true", + "grpc.client.test.security.bundle=grpc_client", + "grpc.client.test.security.authorityOverride=localhost", +}) +@SpringJUnitConfig(classes = {ServiceConfiguration.class, BaseAutoConfiguration.class, SslAutoConfiguration.class}) +@DirtiesContext +public class SelfSignedServerBundleSetupTest extends AbstractSimpleServerClientTest { + + public SelfSignedServerBundleSetupTest() { + log.info("--- SelfSignedServerKeystoreSetupTest ---"); + } + +}