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
This commit is contained in:
Dave Syer
2024-10-11 11:10:07 +01:00
parent 8263972b63
commit fab5c73be1
7 changed files with 125 additions and 7 deletions

View File

@@ -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

View File

@@ -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

Binary file not shown.

View File

@@ -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<io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder> 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<NettyServerBuilder> 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)

View File

@@ -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;
}
}
}

View File

@@ -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 {