Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spring ssl bundle support #1155

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/en/client/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions docs/en/server/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {

Expand Down Expand Up @@ -152,11 +155,12 @@ List<GrpcChannelConfigurer> defaultChannelConfigurers() {
GrpcChannelFactory inProcessOrShadedNettyGrpcChannelFactory(
final GrpcChannelsProperties properties,
final GlobalClientInterceptorRegistry globalClientInterceptorRegistry,
final List<GrpcChannelConfigurer> channelConfigurers) {
final List<GrpcChannelConfigurer> channelConfigurers,
final ObjectProvider<SslBundles> 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);
Expand All @@ -171,11 +175,13 @@ GrpcChannelFactory inProcessOrShadedNettyGrpcChannelFactory(
GrpcChannelFactory inProcessOrNettyGrpcChannelFactory(
final GrpcChannelsProperties properties,
final GlobalClientInterceptorRegistry globalClientInterceptorRegistry,
final List<GrpcChannelConfigurer> channelConfigurers) {
final List<GrpcChannelConfigurer> channelConfigurers,
final ObjectProvider<SslBundles> 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);
Expand All @@ -190,10 +196,12 @@ GrpcChannelFactory inProcessOrNettyGrpcChannelFactory(
GrpcChannelFactory shadedNettyGrpcChannelFactory(
final GrpcChannelsProperties properties,
final GlobalClientInterceptorRegistry globalClientInterceptorRegistry,
final List<GrpcChannelConfigurer> channelConfigurers) {
final List<GrpcChannelConfigurer> channelConfigurers,
final ObjectProvider<SslBundles> 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
Expand All @@ -204,10 +212,12 @@ GrpcChannelFactory shadedNettyGrpcChannelFactory(
GrpcChannelFactory nettyGrpcChannelFactory(
final GrpcChannelsProperties properties,
final GlobalClientInterceptorRegistry globalClientInterceptorRegistry,
final List<GrpcChannelConfigurer> channelConfigurers) {
final List<GrpcChannelConfigurer> channelConfigurers,
final ObjectProvider<SslBundles> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -66,6 +68,8 @@ public abstract class AbstractChannelFactory<T extends ManagedChannelBuilder<T>>
private final GrpcChannelsProperties properties;
protected final GlobalClientInterceptorRegistry globalClientInterceptorRegistry;
protected final List<GrpcChannelConfigurer> channelConfigurers;
@Nullable
protected final SslBundles sslBundles;
/**
* According to <a href="https://groups.google.com/forum/#!topic/grpc-io/-jA_JCiugM8">Thread safety in Grpc java
* clients</a>: {@link ManagedChannel}s should be reused to allow connection reuse.
Expand All @@ -81,14 +85,17 @@ public abstract class AbstractChannelFactory<T extends ManagedChannelBuilder<T>>
* @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<GrpcChannelConfigurer> channelConfigurers) {
final List<GrpcChannelConfigurer> channelConfigurers,
@Nullable final SslBundles sslBundles) {
this.properties = requireNonNull(properties, "properties");
this.globalClientInterceptorRegistry =
requireNonNull(globalClientInterceptorRegistry, "globalClientInterceptorRegistry");
this.channelConfigurers = requireNonNull(channelConfigurers, "channelConfigurers");
this.sslBundles = sslBundles;
}

@Override
Expand Down Expand Up @@ -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!");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public InProcessChannelFactory(final GrpcChannelsProperties properties,
public InProcessChannelFactory(final GrpcChannelsProperties properties,
final GlobalClientInterceptorRegistry globalClientInterceptorRegistry,
final List<GrpcChannelConfigurer> channelConfigurers) {
super(properties, globalClientInterceptorRegistry, channelConfigurers);
super(properties, globalClientInterceptorRegistry, channelConfigurers, null);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -62,11 +66,13 @@ public class NettyChannelFactory extends AbstractChannelFactory<NettyChannelBuil
* @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.
*/
public NettyChannelFactory(final GrpcChannelsProperties properties,
final GlobalClientInterceptorRegistry globalClientInterceptorRegistry,
final List<GrpcChannelConfigurer> channelConfigurers) {
super(properties, globalClientInterceptorRegistry, channelConfigurers);
final List<GrpcChannelConfigurer> channelConfigurers,
@Nullable final SslBundles sslBundles) {
super(properties, globalClientInterceptorRegistry, channelConfigurers, sslBundles);
}

@Override
Expand Down Expand Up @@ -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 {
Expand All @@ -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 =
Expand All @@ -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");
}
Expand All @@ -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()) {
Expand All @@ -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
}
Expand Down
Loading