From fab5c73be1895376d9767f508ddb2c1c8936e6c8 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Fri, 11 Oct 2024 11:10:07 +0100 Subject: [PATCH] Add support for server-side SSL There's no integration test per se, but the GrpcServerApplicationTests main method runs the server securely with an anonymous certificate, so you can test it manually with grpcurl -insecure. See #10 --- .../sample/GrpcServerApplicationTests.java | 2 +- .../test/resources/application-ssl.properties | 5 ++ .../grpc-server/src/test/resources/test.jks | Bin 0 -> 2264 bytes .../GrpcServerFactoryConfigurations.java | 60 +++++++++++++++++- .../server/GrpcServerProperties.java | 47 ++++++++++++++ .../GrpcServerAutoConfigurationTests.java | 18 +++++- .../src/test/resources/test.jks | Bin 0 -> 1276 bytes 7 files changed, 125 insertions(+), 7 deletions(-) create mode 100644 samples/grpc-server/src/test/resources/application-ssl.properties create mode 100644 samples/grpc-server/src/test/resources/test.jks create mode 100644 spring-grpc-spring-boot-autoconfigure/src/test/resources/test.jks 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 0000000000000000000000000000000000000000..6aa9a28053a591e41453e665e5024e8a8cb78b3d GIT binary patch literal 2264 zcmchYX*3iJ7sqE|hQS!q5Mv)4GM2$i#uAFqC`%7x7baWA*i&dRX>3`uq(XS?3XSYp z%38`&ib7E$8j~$cF^}gt?|I+noW8#w?uYxk=iGD8|K9Vzd#pVc0002(2k@T|2@MMI zqxqr2AhQO*TVi`j@((S;e;g;l$#dAA{>vf0kX$R(Qn4oKgGEYjZ5zti2dw?Z6A zh%LuFCNI?9o+Z1duJL-++e#cjO`zlK?u9s030=k_*wD1#-$FbIDRDnA^vo@fm( zzjt(3VJrGOr0iHXSTM|rYN#>RZ@Dp`PwB2zrDQffLvuoR2~V3ReYa0&vU^dXd8isV zsAf*@!8s%xBvHLseXn6f?1kefe(8uAmAbaF$x{Ykzb6c6jdUwY1$y4tFzsj7 zIghr!T#ODfu@Po!a29@kXQ8kY#(LE<0o7?7PQ|eMeY@Equ?R-6*f@Na3o&stDQ=6( zQzDSQhCnS(9Bu9W_~giknP0vECqUsr4_9y_}nEU`cy z4}dApnAip92wMwgzciAFpc3i}+-#Zlq+iF7d1y}d4Qsp8=%l1N8NIs161I`HmkcpQ zY4*CUCFJJf(2!M{`&qQ}3($KeTQ=)mMrBs`DOb;%Of0tC)9he_p~w&CO#DfCgx(%s z{@|D(brX_Gb}ZDLmGej*JgEl0Et>q~kgTXuJg-PwvRjNx8sBbIShxD=xOySzw{;^X zAvrh5HTg>Xq@<{#^!Kg}B?qz@b<{ebD)yaSf&RChBIJQo-?Ahzw@qopSe^e&>^IuU zydM4Y1_C&>k7u|}=; z63R7$H6zat=hNExxEwXu1fQ*ytuEkP!{w{|#6TIEq1#*ck=6_NM*ILF65tmD-O5&R zMI!-MT<3U~t@}(CN4@RlZ~1I>C=!ywF)dNI{VvH;5Y3(Z4jY^%_c&fsm4Q`<1g|qX z&!h29jXjVE3nJnet*L)XL?-8<>qDbVGP%i^NwOZfwWO7?Mr!X7 zl}sG@9S_5}}td}$xrWIYY=e(VVBiv%A+M-{M z!3_^Tc=pV?niT!{D`!{e@W;MvrZ(OER{x7itVAtwE~spPtPtma|J=5dv&_oE!5H#` zdgXJ;+gJ4hI}*9QX9jpL`Gb)yCe%1}t!&O-^sihyZys%%5uF~WhsR_w(q7;vV5d4P zr%ZUA2}kO+L^2ePTgGT9Ua71w<+)poSyjTdLq&xbUn`<6&SpwFp(HRHUyU6J3WZ_! zfztko79+94Tq%mTYj53(RYcL&1~5`I#+w3`(Q|r+P(aT z%?r(^?IWw~19CB&uvXf(f7&BnEE{zwK4piVU`I4j1j?v5d4N<7VUJ8nM`$7S*mfKR z#9-JzPRZ?{M!@L+0N^V)IyeeP2T|^UK|m0QD+Ibs!wEoml^N!YO#vW~j~jraX(0A3 z6Kux?IRLez`O^X;{!4g%BhcRn>^H*qKZ3*|{_YGuz)KCJcu;)DSES5D2tDE`C02YR0R%Vy1T7k|RQ;3g<0icA$AuP0pOvc~jGl zz+NeKv_FT_;GWK&8XlDUv&hv9kxg?@c!bu?83i=YQ$S!K09Y)Glg3Hz?@|)ZCBlVz zP8i}#XZkMoje3I=h&I!!s_m?Qi@1MR`yv7X*yEs47qOs^t^?&=;*IQ!q&)gq_Sx5* z?fhU8Q*PSe*w7y)FH#P!9R^Xw!lTT+zI39L<&8cViaj$A(Z2Cg7!{V?uuyi#vlNCg z40i}2ivw&y&1-&Nh&WMG`&aIt>)(#tKTJ}^@696Kw1-{IzSOTnFF+0@k$o3%ZHS;Q#;t literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0fc3e802f75461dd074facb9611d350db4d5960f GIT binary patch literal 1276 zcmezO_TO6u1_mZ5W@O+hNi8nXP0YzmEM{O}OjTL=XFE`?-k{cikBv*4jgf^>i%F1? zk(GfZ`?F{4vBFug6<%MmmXJ+)mEgQoslXV6!5_H^yana6V1?kw1w zbE0Bi&GHlLHzfosgjrwL)qKcc5I;l0LF?W2lpQg%-cHp$l(#o)?Jkath1@gQN{hG8 zig@wKv#0R7vd_QC=JG%%Ffy=4=$RT=0v*d`(8R=M(8RcU0W%XL6BCP-)w&Y~JZv0V zZ64=rS(uqv84M~6g$xAPm_u3EggJBalM{0?@{3DgVjNh+*s+LlVG-lTBF2m)W*{fd zYiMC$VQ64zW@K(?5e4L0B5?=MWswHLZ0z7LVq$~_7BeF|vl9agPmO-znfkD()@R+bGrT$(AXmN24#zv*XRX z#$UNu(Lmln78u;Jd@N!tBKmU@J0!OJc3G%!N>OO@P1n+F-CorAVRmOQaA8six!iWP z)M3lXpnJ*TI=kIlH(Yxia-ls?xvctEx&P5B6()tKm`>%bo~@fX9{j%TtMU1G!|pw& zZ6BRjIqQ^`bIxR@OmMno&8^H%tpq36Esh&T(+MJ_la+#pV>+3scS%