From 05e6240672968403a92bd1cc09b604763b4bcba5 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Mon, 4 Nov 2024 18:17:59 +0000 Subject: [PATCH] Support for mTLS in client Fixes #10 --- .../sample/GrpcServerIntegrationTests.java | 4 +- .../client/ChannelCredentialsProvider.java | 30 +++++ .../client/DefaultGrpcChannelFactory.java | 17 +-- .../grpc/client/NettyGrpcChannelFactory.java | 10 -- .../client/ShadedNettyGrpcChannelFactory.java | 10 -- .../modules/ROOT/partials/_configprops.adoc | 2 + .../GrpcChannelFactoryConfigurations.java | 114 ++---------------- .../client/GrpcClientAutoConfiguration.java | 55 ++++++--- .../client/GrpcClientProperties.java | 10 ++ .../NettyChannelCredentialsProvider.java | 69 +++++++++++ .../client/NettyChannelFactoryHelper.java | 47 -------- ...ShadedNettyChannelCredentialsProvider.java | 69 +++++++++++ .../ShadedNettyChannelFactoryHelper.java | 48 -------- .../GrpcServerFactoryConfigurations.java | 6 +- .../server/GrpcServerProperties.java | 14 +++ 15 files changed, 257 insertions(+), 248 deletions(-) create mode 100644 spring-grpc-core/src/main/java/org/springframework/grpc/client/ChannelCredentialsProvider.java create mode 100644 spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/NettyChannelCredentialsProvider.java delete mode 100644 spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/NettyChannelFactoryHelper.java create mode 100644 spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/ShadedNettyChannelCredentialsProvider.java delete mode 100644 spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/ShadedNettyChannelFactoryHelper.java diff --git a/samples/grpc-server/src/test/java/org/springframework/grpc/sample/GrpcServerIntegrationTests.java b/samples/grpc-server/src/test/java/org/springframework/grpc/sample/GrpcServerIntegrationTests.java index b61383e..d4f5ac5 100644 --- a/samples/grpc-server/src/test/java/org/springframework/grpc/sample/GrpcServerIntegrationTests.java +++ b/samples/grpc-server/src/test/java/org/springframework/grpc/sample/GrpcServerIntegrationTests.java @@ -18,7 +18,6 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledOnOs; @@ -122,12 +121,13 @@ void clientChannelWithSsl(@Autowired GrpcChannelFactory channels) { @Nested @SpringBootTest(properties = { "spring.grpc.server.port=0", "spring.grpc.server.ssl.client-auth=REQUIRE", + "spring.grpc.server.ssl.secure=false", "spring.grpc.client.channels.test-channel.address=static://0.0.0.0:${local.grpc.port}", + "spring.grpc.client.channels.test-channel.ssl.bundle=ssltest", "spring.grpc.client.channels.test-channel.negotiation-type=TLS", "spring.grpc.client.channels.test-channel.secure=false" }) @ActiveProfiles("ssl") @DirtiesContext - @Disabled("Requires client certificate") class ServerWithClientAuth { @Test diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/client/ChannelCredentialsProvider.java b/spring-grpc-core/src/main/java/org/springframework/grpc/client/ChannelCredentialsProvider.java new file mode 100644 index 0000000..ec249c6 --- /dev/null +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/client/ChannelCredentialsProvider.java @@ -0,0 +1,30 @@ +/* + * Copyright 2024-2024 the original author or 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 + * + * https://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 org.springframework.grpc.client; + +import io.grpc.ChannelCredentials; +import io.grpc.InsecureChannelCredentials; + +/** + * A provider for obtaining channel credentials for gRPC client. + */ +public interface ChannelCredentialsProvider { + + static final ChannelCredentialsProvider INSECURE = path -> InsecureChannelCredentials.create(); + + ChannelCredentials getChannelCredentials(String path); + +} diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/client/DefaultGrpcChannelFactory.java b/spring-grpc-core/src/main/java/org/springframework/grpc/client/DefaultGrpcChannelFactory.java index fd281d6..5b9bbd7 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/client/DefaultGrpcChannelFactory.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/client/DefaultGrpcChannelFactory.java @@ -37,6 +37,8 @@ public class DefaultGrpcChannelFactory implements GrpcChannelFactory, Disposable private final List configurers = new ArrayList<>(); + private ChannelCredentialsProvider credentials = ChannelCredentialsProvider.INSECURE; + private VirtualTargets targets = VirtualTargets.DEFAULT; public DefaultGrpcChannelFactory() { @@ -51,10 +53,15 @@ public void setVirtualTargets(VirtualTargets targets) { this.targets = targets; } + public void setCredentialsProvider(ChannelCredentialsProvider credentials) { + this.credentials = credentials; + } + @Override public ManagedChannelBuilder createChannel(String authority) { ManagedChannelBuilder target = builders.computeIfAbsent(authority, path -> { - ManagedChannelBuilder builder = newChannel(targets.getTarget(path)); + ManagedChannelBuilder builder = newChannel(targets.getTarget(path), + credentials.getChannelCredentials(path)); for (GrpcChannelConfigurer configurer : configurers) { configurer.configure(path, builder); } @@ -64,12 +71,8 @@ public ManagedChannelBuilder createChannel(String authority) { } - protected ChannelCredentials channelCredentials(String path) { - return InsecureChannelCredentials.create(); - } - - protected ManagedChannelBuilder newChannel(String path) { - return Grpc.newChannelBuilder(path, channelCredentials(path)); + protected ManagedChannelBuilder newChannel(String path, ChannelCredentials creds) { + return Grpc.newChannelBuilder(path, creds); } @Override diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/client/NettyGrpcChannelFactory.java b/spring-grpc-core/src/main/java/org/springframework/grpc/client/NettyGrpcChannelFactory.java index 57d7c4e..5a42ec6 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/client/NettyGrpcChannelFactory.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/client/NettyGrpcChannelFactory.java @@ -17,20 +17,10 @@ import java.util.List; -import io.grpc.ManagedChannelBuilder; -import io.grpc.netty.NettyChannelBuilder; - public class NettyGrpcChannelFactory extends DefaultGrpcChannelFactory { public NettyGrpcChannelFactory(List configurers) { super(configurers); } - protected ManagedChannelBuilder newChannel(String path) { - if (path.startsWith("unix:")) { - return super.newChannel(path); - } - return NettyChannelBuilder.forTarget(path); - } - } diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/client/ShadedNettyGrpcChannelFactory.java b/spring-grpc-core/src/main/java/org/springframework/grpc/client/ShadedNettyGrpcChannelFactory.java index b88e082..94629df 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/client/ShadedNettyGrpcChannelFactory.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/client/ShadedNettyGrpcChannelFactory.java @@ -17,20 +17,10 @@ import java.util.List; -import io.grpc.ManagedChannelBuilder; -import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; - public class ShadedNettyGrpcChannelFactory extends DefaultGrpcChannelFactory { public ShadedNettyGrpcChannelFactory(List configurers) { super(configurers); } - protected ManagedChannelBuilder newChannel(String path) { - if (path.startsWith("unix:")) { - return super.newChannel(path); - } - return NettyChannelBuilder.forTarget(path); - } - } diff --git a/spring-grpc-docs/src/main/antora/modules/ROOT/partials/_configprops.adoc b/spring-grpc-docs/src/main/antora/modules/ROOT/partials/_configprops.adoc index b3f535e..3b5481c 100644 --- a/spring-grpc-docs/src/main/antora/modules/ROOT/partials/_configprops.adoc +++ b/spring-grpc-docs/src/main/antora/modules/ROOT/partials/_configprops.adoc @@ -30,6 +30,8 @@ |spring.grpc.server.port | `+++9090+++` | Server port to listen on. When the value is 0, a random available port is selected. The default is 9090. |spring.grpc.server.shutdown-grace-period | `+++30s+++` | Maximum time to wait for the server to gracefully shutdown. When the value is negative, the server waits forever. When the value is 0, the server will force shutdown immediately. The default is 30 seconds. |spring.grpc.server.ssl.bundle | | SSL bundle name. +|spring.grpc.server.ssl.client-auth | | Client authentication mode. |spring.grpc.server.ssl.enabled | | Whether to enable SSL support. Enabled automatically if "bundle" is provided unless specified otherwise. +|spring.grpc.server.ssl.secure | `+++true+++` | Flag to indicate that client authentication is secure (i.e. certificates are checked). Do not set this to false in production. |=== \ No newline at end of file diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcChannelFactoryConfigurations.java b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcChannelFactoryConfigurations.java index d0bb51d..220e7ed 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcChannelFactoryConfigurations.java +++ b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcChannelFactoryConfigurations.java @@ -15,133 +15,39 @@ */ package org.springframework.grpc.autoconfigure.client; -import java.util.List; - -import javax.net.ssl.SSLException; - import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.ssl.SslBundles; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.grpc.autoconfigure.client.GrpcClientProperties.NamedChannel; -import org.springframework.grpc.client.DefaultGrpcChannelFactory; -import org.springframework.grpc.client.GrpcChannelConfigurer; -import org.springframework.grpc.client.GrpcChannelFactory; -import org.springframework.grpc.client.NegotiationType; -import org.springframework.grpc.client.NettyGrpcChannelFactory; -import org.springframework.grpc.client.ShadedNettyGrpcChannelFactory; -import org.springframework.grpc.client.VirtualTargets; +import org.springframework.grpc.client.ChannelCredentialsProvider; -import io.grpc.netty.GrpcSslContexts; import io.grpc.netty.NettyChannelBuilder; -import io.netty.handler.ssl.util.InsecureTrustManagerFactory; public class GrpcChannelFactoryConfigurations { @Configuration(proxyBeanMethods = false) @ConditionalOnClass(io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.class) - @ConditionalOnMissingBean(GrpcChannelFactory.class) + @ConditionalOnMissingBean(ChannelCredentialsProvider.class) public static class ShadedNettyChannelFactoryConfiguration { @Bean - public DefaultGrpcChannelFactory defaultGrpcChannelFactory(final List configurers, - GrpcClientProperties channels) { - DefaultGrpcChannelFactory factory = new ShadedNettyGrpcChannelFactory(configurers); - factory.setVirtualTargets(new NamedChannelVirtualTargets(channels)); - return factory; - } - - @Bean - public GrpcChannelConfigurer secureChannelConfigurer(GrpcClientProperties channels) { - - return (authority, input) -> { - NamedChannel channel = channels.getChannel(authority); - if (!authority.startsWith("unix:") - && input instanceof io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder builder) { - builder.negotiationType(of(channel.getNegotiationType())); - try { - if (!channel.isSecure()) { - builder.sslContext(io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts.forClient() - .trustManager( - io.grpc.netty.shaded.io.netty.handler.ssl.util.InsecureTrustManagerFactory.INSTANCE) - .build()); - } - } - catch (SSLException e) { - throw new IllegalStateException("Failed to create SSL context", e); - } - } - }; - - } - - private static io.grpc.netty.shaded.io.grpc.netty.NegotiationType of(final NegotiationType negotiationType) { - return switch (negotiationType) { - case PLAINTEXT -> io.grpc.netty.shaded.io.grpc.netty.NegotiationType.PLAINTEXT; - case PLAINTEXT_UPGRADE -> io.grpc.netty.shaded.io.grpc.netty.NegotiationType.PLAINTEXT_UPGRADE; - case TLS -> io.grpc.netty.shaded.io.grpc.netty.NegotiationType.TLS; - }; + public ChannelCredentialsProvider channelCredentialsProvider(GrpcClientProperties channels, + SslBundles bundles) { + return new ShadedNettyChannelCredentialsProvider(bundles, channels); } } @Configuration(proxyBeanMethods = false) @ConditionalOnClass(NettyChannelBuilder.class) - @ConditionalOnMissingBean(GrpcChannelFactory.class) + @ConditionalOnMissingBean(ChannelCredentialsProvider.class) public static class NettyChannelFactoryConfiguration { @Bean - public DefaultGrpcChannelFactory defaultGrpcChannelFactory(final List configurers, - GrpcClientProperties channels) { - DefaultGrpcChannelFactory factory = new NettyGrpcChannelFactory(configurers); - factory.setVirtualTargets(new NamedChannelVirtualTargets(channels)); - return factory; - } - - @Bean - public GrpcChannelConfigurer secureChannelConfigurer(GrpcClientProperties channels) { - - return (authority, input) -> { - NamedChannel channel = channels.getChannel(authority); - if (!authority.startsWith("unix:") && input instanceof NettyChannelBuilder builder) { - builder.negotiationType(of(channel.getNegotiationType())); - try { - if (!channel.isSecure()) { - builder.sslContext(GrpcSslContexts.forClient() - .trustManager(InsecureTrustManagerFactory.INSTANCE) - .build()); - } - } - catch (SSLException e) { - throw new IllegalStateException("Failed to create SSL context", e); - } - } - }; - - } - - private static io.grpc.netty.NegotiationType of(final NegotiationType negotiationType) { - return switch (negotiationType) { - case PLAINTEXT -> io.grpc.netty.NegotiationType.PLAINTEXT; - case PLAINTEXT_UPGRADE -> io.grpc.netty.NegotiationType.PLAINTEXT_UPGRADE; - case TLS -> io.grpc.netty.NegotiationType.TLS; - }; - } - - } - - static class NamedChannelVirtualTargets implements VirtualTargets { - - private final GrpcClientProperties channels; - - NamedChannelVirtualTargets(GrpcClientProperties channels) { - this.channels = channels; - } - - @Override - public String getTarget(String authority) { - NamedChannel channel = this.channels.getChannel(authority); - return channels.getTarget(channel.getAddress()); + public ChannelCredentialsProvider channelCredentialsProvider(GrpcClientProperties channels, + SslBundles bundles) { + return new NettyChannelCredentialsProvider(bundles, channels); } } diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcClientAutoConfiguration.java b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcClientAutoConfiguration.java index 7341750..9388d8d 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcClientAutoConfiguration.java +++ b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcClientAutoConfiguration.java @@ -15,20 +15,26 @@ */ package org.springframework.grpc.autoconfigure.client; +import java.util.List; import java.util.concurrent.TimeUnit; -import io.grpc.CompressorRegistry; -import io.grpc.DecompressorRegistry; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +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.context.annotation.Import; import org.springframework.grpc.autoconfigure.client.GrpcClientProperties.NamedChannel; import org.springframework.grpc.autoconfigure.common.codec.GrpcCodecConfiguration; +import org.springframework.grpc.client.ChannelCredentialsProvider; +import org.springframework.grpc.client.DefaultGrpcChannelFactory; import org.springframework.grpc.client.GrpcChannelConfigurer; +import org.springframework.grpc.client.GrpcChannelFactory; +import org.springframework.grpc.client.VirtualTargets; + +import io.grpc.CompressorRegistry; +import io.grpc.DecompressorRegistry; @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(GrpcClientProperties.class) @@ -37,26 +43,21 @@ public class GrpcClientAutoConfiguration { @Bean - public GrpcChannelConfigurer sslGrpcChannelConfigurer(GrpcClientProperties channels, SslBundles bundles) { + @ConditionalOnMissingBean(GrpcChannelFactory.class) + public DefaultGrpcChannelFactory defaultGrpcChannelFactory(final List configurers, + ChannelCredentialsProvider credentials, GrpcClientProperties channels, SslBundles bundles) { + DefaultGrpcChannelFactory factory = new DefaultGrpcChannelFactory(configurers); + factory.setCredentialsProvider(credentials); + factory.setVirtualTargets(new NamedChannelVirtualTargets(channels)); + return factory; + } + + @Bean + public GrpcChannelConfigurer sslGrpcChannelConfigurer(GrpcClientProperties channels) { return (authority, builder) -> { for (String name : channels.getChannels().keySet()) { if (authority.equals(name)) { NamedChannel channel = channels.getChannels().get(name); - if (channel.getSsl().isEnabled() && channel.getSsl().getBundle() != null) { - SslBundle bundle = bundles.getBundle(channel.getSsl().getBundle()); - if (NettyChannelFactoryHelper.isAvailable()) { - NettyChannelFactoryHelper.sslContext(builder, bundle); - } - else if (ShadedNettyChannelFactoryHelper.isAvailable()) { - ShadedNettyChannelFactoryHelper.sslContext(builder, bundle); - } - else { - throw new IllegalStateException("Netty is not available"); - } - } - else { - // builder.usePlaintext(); - } if (channel.getUserAgent() != null) { builder.userAgent(channel.getUserAgent()); } @@ -96,4 +97,20 @@ GrpcChannelConfigurer decompressionClientConfigurer(DecompressorRegistry registr return (name, builder) -> builder.decompressorRegistry(registry); } + static class NamedChannelVirtualTargets implements VirtualTargets { + + private final GrpcClientProperties channels; + + NamedChannelVirtualTargets(GrpcClientProperties channels) { + this.channels = channels; + } + + @Override + public String getTarget(String authority) { + NamedChannel channel = this.channels.getChannel(authority); + return channels.getTarget(channel.getAddress()); + } + + } + } diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcClientProperties.java b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcClientProperties.java index d9ccab2..58f3374 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcClientProperties.java +++ b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcClientProperties.java @@ -24,6 +24,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.convert.DataSizeUnit; import org.springframework.boot.convert.DurationUnit; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; import org.springframework.context.EnvironmentAware; import org.springframework.core.env.Environment; import org.springframework.core.env.StandardEnvironment; @@ -523,4 +525,12 @@ public void setBundle(String bundle) { } + public SslBundle sslBundle(SslBundles bundles, String path) { + NamedChannel channel = this.getChannel(path); + if (!channel.getSsl().isEnabled()) { + return null; + } + return bundles.getBundle(this.getChannel(path).getSsl().getBundle()); + } + } diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/NettyChannelCredentialsProvider.java b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/NettyChannelCredentialsProvider.java new file mode 100644 index 0000000..81d4494 --- /dev/null +++ b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/NettyChannelCredentialsProvider.java @@ -0,0 +1,69 @@ +/* + * Copyright 2024-2024 the original author or 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 + * + * https://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 org.springframework.grpc.autoconfigure.client; + +import javax.net.ssl.TrustManagerFactory; + +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.grpc.autoconfigure.client.GrpcClientProperties.NamedChannel; +import org.springframework.grpc.client.ChannelCredentialsProvider; +import org.springframework.grpc.client.NegotiationType; + +import io.grpc.ChannelCredentials; +import io.grpc.InsecureChannelCredentials; +import io.grpc.TlsChannelCredentials; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; + +public class NettyChannelCredentialsProvider implements ChannelCredentialsProvider { + + private final GrpcClientProperties channels; + + private final SslBundles bundles; + + public NettyChannelCredentialsProvider(SslBundles bundles, GrpcClientProperties channels) { + this.bundles = bundles; + this.channels = channels; + } + + @Override + public ChannelCredentials getChannelCredentials(String path) { + SslBundle bundle = channels.sslBundle(bundles, path); + NamedChannel channel = channels.getChannel(path); + if (!channel.getSsl().isEnabled() && channel.getNegotiationType() == NegotiationType.PLAINTEXT) { + return InsecureChannelCredentials.create(); + } + if (bundle != null) { + TrustManagerFactory trustManager = channel.isSecure() ? bundle.getManagers().getTrustManagerFactory() + : InsecureTrustManagerFactory.INSTANCE; + return TlsChannelCredentials.newBuilder() + .keyManager(bundle.getManagers().getKeyManagerFactory().getKeyManagers()) + .trustManager(trustManager.getTrustManagers()) + .build(); + } + else { + if (channel.isSecure()) { + return TlsChannelCredentials.create(); + } + else { + return TlsChannelCredentials.newBuilder() + .trustManager(InsecureTrustManagerFactory.INSTANCE.getTrustManagers()) + .build(); + } + } + } + +} diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/NettyChannelFactoryHelper.java b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/NettyChannelFactoryHelper.java deleted file mode 100644 index 45fd3ce..0000000 --- a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/NettyChannelFactoryHelper.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2024-2024 the original author or 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 - * - * https://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 org.springframework.grpc.autoconfigure.client; - -import javax.net.ssl.SSLException; - -import org.springframework.boot.ssl.SslBundle; -import org.springframework.util.ClassUtils; - -import io.grpc.ManagedChannelBuilder; -import io.grpc.netty.NettyChannelBuilder; -import io.netty.handler.ssl.SslContextBuilder; - -class NettyChannelFactoryHelper { - - private static final boolean AVAILABLE = ClassUtils.isPresent("io.grpc.netty.NettyChannelBuilder", null); - - public static boolean isAvailable() { - return AVAILABLE; - } - - public static void sslContext(ManagedChannelBuilder builder, SslBundle bundle) { - if (builder instanceof NettyChannelBuilder nettyBuilder) { - try { - nettyBuilder.sslContext( - SslContextBuilder.forClient().keyManager(bundle.getManagers().getKeyManagerFactory()).build()); - } - catch (SSLException e) { - throw new IllegalStateException("Failed to create SSL context", e); - } - } - } - -} diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/ShadedNettyChannelCredentialsProvider.java b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/ShadedNettyChannelCredentialsProvider.java new file mode 100644 index 0000000..f4cb0b2 --- /dev/null +++ b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/ShadedNettyChannelCredentialsProvider.java @@ -0,0 +1,69 @@ +/* + * Copyright 2024-2024 the original author or 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 + * + * https://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 org.springframework.grpc.autoconfigure.client; + +import javax.net.ssl.TrustManagerFactory; + +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.grpc.autoconfigure.client.GrpcClientProperties.NamedChannel; +import org.springframework.grpc.client.ChannelCredentialsProvider; +import org.springframework.grpc.client.NegotiationType; + +import io.grpc.ChannelCredentials; +import io.grpc.InsecureChannelCredentials; +import io.grpc.TlsChannelCredentials; +import io.grpc.netty.shaded.io.netty.handler.ssl.util.InsecureTrustManagerFactory; + +public class ShadedNettyChannelCredentialsProvider implements ChannelCredentialsProvider { + + private final GrpcClientProperties channels; + + private final SslBundles bundles; + + public ShadedNettyChannelCredentialsProvider(SslBundles bundles, GrpcClientProperties channels) { + this.bundles = bundles; + this.channels = channels; + } + + @Override + public ChannelCredentials getChannelCredentials(String path) { + SslBundle bundle = channels.sslBundle(bundles, path); + NamedChannel channel = channels.getChannel(path); + if (!channel.getSsl().isEnabled() && channel.getNegotiationType() == NegotiationType.PLAINTEXT) { + return InsecureChannelCredentials.create(); + } + if (bundle != null) { + TrustManagerFactory trustManager = channel.isSecure() ? bundle.getManagers().getTrustManagerFactory() + : InsecureTrustManagerFactory.INSTANCE; + return TlsChannelCredentials.newBuilder() + .keyManager(bundle.getManagers().getKeyManagerFactory().getKeyManagers()) + .trustManager(trustManager.getTrustManagers()) + .build(); + } + else { + if (channel.isSecure()) { + return TlsChannelCredentials.create(); + } + else { + return TlsChannelCredentials.newBuilder() + .trustManager(InsecureTrustManagerFactory.INSTANCE.getTrustManagers()) + .build(); + } + } + } + +} diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/ShadedNettyChannelFactoryHelper.java b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/ShadedNettyChannelFactoryHelper.java deleted file mode 100644 index 5cd4559..0000000 --- a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/ShadedNettyChannelFactoryHelper.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2024-2024 the original author or 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 - * - * https://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 org.springframework.grpc.autoconfigure.client; - -import javax.net.ssl.SSLException; - -import org.springframework.boot.ssl.SslBundle; -import org.springframework.util.ClassUtils; - -import io.grpc.ManagedChannelBuilder; -import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; -import io.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder; - -class ShadedNettyChannelFactoryHelper { - - private static final boolean AVAILABLE = ClassUtils - .isPresent("io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder", null); - - public static boolean isAvailable() { - return AVAILABLE; - } - - public static void sslContext(ManagedChannelBuilder builder, SslBundle bundle) { - if (builder instanceof NettyChannelBuilder nettyBuilder) { - try { - nettyBuilder.sslContext( - SslContextBuilder.forClient().keyManager(bundle.getManagers().getKeyManagerFactory()).build()); - } - catch (SSLException e) { - throw new IllegalStateException("Failed to create SSL context", e); - } - } - } - -} 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 f88dc19..bbd9761 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 @@ -35,6 +35,7 @@ import org.springframework.grpc.server.ShadedNettyGrpcServerFactory; import io.grpc.netty.NettyServerBuilder; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; /** * Configurations for {@link GrpcServerFactory gRPC server factories}. @@ -61,6 +62,8 @@ ShadedNettyGrpcServerFactory shadedNettyGrpcServerFactory(GrpcServerProperties p if (properties.getSsl().isEnabled()) { SslBundle bundle = bundles.getBundle(properties.getSsl().getBundle()); keyManager = bundle.getManagers().getKeyManagerFactory(); + trustManager = properties.getSsl().isSecure() ? bundle.getManagers().getTrustManagerFactory() + : io.grpc.netty.shaded.io.netty.handler.ssl.util.InsecureTrustManagerFactory.INSTANCE; trustManager = bundle.getManagers().getTrustManagerFactory(); } ShadedNettyGrpcServerFactory factory = new ShadedNettyGrpcServerFactory(properties.getAddress(), @@ -89,7 +92,8 @@ NettyGrpcServerFactory nettyGrpcServerFactory(GrpcServerProperties properties, if (properties.getSsl().isEnabled()) { SslBundle bundle = bundles.getBundle(properties.getSsl().getBundle()); keyManager = bundle.getManagers().getKeyManagerFactory(); - trustManager = bundle.getManagers().getTrustManagerFactory(); + trustManager = properties.getSsl().isSecure() ? bundle.getManagers().getTrustManagerFactory() + : InsecureTrustManagerFactory.INSTANCE; } NettyGrpcServerFactory factory = new NettyGrpcServerFactory(properties.getAddress(), builderCustomizers, keyManager, trustManager, properties.getSsl().getClientAuth()); 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 601950b..dfd187e 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 @@ -265,6 +265,12 @@ public static class Ssl { */ private String bundle; + /** + * Flag to indicate that client authentication is secure (i.e. certificates are + * checked). Do not set this to false in production. + */ + private boolean secure = true; + public boolean isEnabled() { return (this.enabled != null) ? this.enabled : this.bundle != null; } @@ -299,6 +305,14 @@ public ClientAuth getClientAuth() { return clientAuth; } + public void setSecure(boolean secure) { + this.secure = secure; + } + + public boolean isSecure() { + return this.secure; + } + } }