diff --git a/samples/grpc-server/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java b/samples/grpc-server/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java index eae86b8..bfc73df 100644 --- a/samples/grpc-server/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java +++ b/samples/grpc-server/src/test/java/org/springframework/grpc/sample/GrpcServerApplicationTests.java @@ -24,7 +24,7 @@ public class GrpcServerApplicationTests { private static Log log = LogFactory.getLog(GrpcServerApplicationTests.class); public static void main(String[] args) { - new SpringApplicationBuilder(GrpcServerApplication.class, ExtraConfiguration.class).run(args); + new SpringApplicationBuilder(GrpcServerApplication.class, ExtraConfiguration.class).profiles("ssl").run(args); } @Autowired diff --git a/samples/grpc-server/src/test/resources/application-ssl.properties b/samples/grpc-server/src/test/resources/application-ssl.properties new file mode 100644 index 0000000..50ad5c4 --- /dev/null +++ b/samples/grpc-server/src/test/resources/application-ssl.properties @@ -0,0 +1,5 @@ +spring.grpc.server.ssl.bundle=ssltest +spring.ssl.bundle.jks.ssltest.keystore.location=classpath:test.jks +spring.ssl.bundle.jks.ssltest.keystore.password=secret +spring.ssl.bundle.jks.ssltest.keystore.type=JKS +spring.ssl.bundle.jks.ssltest.key.password=password \ No newline at end of file diff --git a/samples/grpc-server/src/test/resources/test.jks b/samples/grpc-server/src/test/resources/test.jks new file mode 100644 index 0000000..6aa9a28 Binary files /dev/null and b/samples/grpc-server/src/test/resources/test.jks differ diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerFactoryConfigurations.java b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerFactoryConfigurations.java index 37820a6..952f649 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerFactoryConfigurations.java +++ b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerFactoryConfigurations.java @@ -18,14 +18,14 @@ package org.springframework.grpc.autoconfigure.server; import java.util.List; -import io.grpc.BindableService; -import io.grpc.ServerBuilder; -import io.grpc.netty.NettyServerBuilder; +import javax.net.ssl.SSLException; import org.springframework.beans.factory.ObjectProvider; 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.SslBundle; +import org.springframework.boot.ssl.SslBundles; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.grpc.server.DefaultGrpcServerFactory; @@ -34,6 +34,15 @@ import org.springframework.grpc.server.NettyGrpcServerFactory; import org.springframework.grpc.server.ServerBuilderCustomizer; import org.springframework.grpc.server.ShadedNettyGrpcServerFactory; +import io.grpc.BindableService; +import io.grpc.ServerBuilder; +import io.grpc.netty.GrpcSslContexts; +import io.grpc.netty.NettyServerBuilder; +import io.netty.handler.ssl.ClientAuth; +import io.netty.handler.ssl.JdkSslContext; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; + /** * Configurations for {@link GrpcServerFactory gRPC server factories}. * @@ -60,6 +69,29 @@ class GrpcServerFactoryConfigurations { return factory; } + @Bean + ServerBuilderCustomizer sslServerCustomizer( + GrpcServerProperties properties, SslBundles bundles) { + if (properties.getSsl().isEnabled()) { + SslBundle bundle = bundles.getBundle(properties.getSsl().getBundle()); + return builder -> { + try { + builder.sslContext(io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts + .configure(io.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder + .forServer(bundle.getManagers().getKeyManagerFactory())) + .build()); + } + catch (SSLException e) { + throw new IllegalStateException("Failed to create SSL context", e); + } + }; + } + else { + return builder -> { + }; + } + } + } @Configuration(proxyBeanMethods = false) @@ -81,6 +113,28 @@ class GrpcServerFactoryConfigurations { return factory; } + @Bean + ServerBuilderCustomizer sslServerCustomizer(GrpcServerProperties properties, + SslBundles bundles) { + if (properties.getSsl().isEnabled()) { + SslBundle bundle = bundles.getBundle(properties.getSsl().getBundle()); + return builder -> { + try { + builder.sslContext(GrpcSslContexts + .configure(SslContextBuilder.forServer(bundle.getManagers().getKeyManagerFactory())) + .build()); + } + catch (SSLException e) { + throw new IllegalStateException("Failed to create SSL context", e); + } + }; + } + else { + return builder -> { + }; + } + } + } @Configuration(proxyBeanMethods = false) diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerProperties.java b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerProperties.java index c0d68d5..c0b0ca8 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerProperties.java +++ b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerProperties.java @@ -215,4 +215,51 @@ public class GrpcServerProperties { } + private final Ssl ssl = new Ssl(); + + public Ssl getSsl() { + return this.ssl; + } + + public static class Ssl { + + /** + * Whether to enable SSL support. Enabled automatically if "bundle" is provided + * unless specified otherwise. + */ + private Boolean enabled; + + /** + * SSL bundle name. + */ + private String bundle; + + public boolean isEnabled() { + return (this.enabled != null) ? this.enabled : this.bundle != null; + } + + public void copyDefaultsFrom(Ssl config) { + if (this.enabled == null) { + this.enabled = config.enabled; + } + if (this.bundle == null) { + this.bundle = config.bundle; + } + + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getBundle() { + return this.bundle; + } + + public void setBundle(String bundle) { + this.bundle = bundle; + } + + } + } diff --git a/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfigurationTests.java b/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfigurationTests.java index 4b9266b..7928251 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfigurationTests.java +++ b/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerAutoConfigurationTests.java @@ -33,6 +33,7 @@ import org.mockito.Mockito; import org.mockito.stubbing.Answer; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; @@ -64,7 +65,7 @@ class GrpcServerAutoConfigurationTests { when(service.bindService()).thenReturn(serviceDefinition); // NOTE: we use noop server lifecycle to avoid startup return new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(GrpcServerAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(GrpcServerAutoConfiguration.class, SslAutoConfiguration.class)) .withBean("noopServerLifecycle", GrpcServerLifecycle.class, Mockito::mock) .withBean(BindableService.class, () -> service); } @@ -75,7 +76,7 @@ class GrpcServerAutoConfigurationTests { when(service.bindService()).thenReturn(serviceDefinition); // NOTE: we use noop server lifecycle to avoid startup return new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(GrpcServerAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(GrpcServerAutoConfiguration.class, SslAutoConfiguration.class)) .withBean(BindableService.class, () -> service); } @@ -121,7 +122,7 @@ class GrpcServerAutoConfigurationTests { .withUserConfiguration(ServerBuilderCustomizersConfig.class) .run((context) -> assertThat(context).getBean(ServerBuilderCustomizers.class) .extracting("customizers", InstanceOfAssertFactories.list(ServerBuilderCustomizer.class)) - .containsExactly(ServerBuilderCustomizersConfig.CUSTOMIZER_BAR, + .contains(ServerBuilderCustomizersConfig.CUSTOMIZER_BAR, ServerBuilderCustomizersConfig.CUSTOMIZER_FOO)); } @@ -253,6 +254,17 @@ class GrpcServerAutoConfigurationTests { })); } + @Test + void nettyServerFactoryAutoConfiguredWithSsl() { + serverFactoryAutoConfiguredAsExpected( + this.contextRunner() + .withPropertyValues("spring.grpc.server.ssl.bundle=ssltest", + "spring.ssl.bundle.jks.ssltest.keystore.location=classpath:test.jks") + .withClassLoader( + new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class)), + NettyGrpcServerFactory.class); + } + @Configuration(proxyBeanMethods = false) static class ServerBuilderCustomizersConfig { diff --git a/spring-grpc-spring-boot-autoconfigure/src/test/resources/test.jks b/spring-grpc-spring-boot-autoconfigure/src/test/resources/test.jks new file mode 100644 index 0000000..0fc3e80 Binary files /dev/null and b/spring-grpc-spring-boot-autoconfigure/src/test/resources/test.jks differ