Skip to content

Commit 1552e18

Browse files
author
Alexander Furer
committed
TLS support
1 parent 6a2026b commit 1552e18

File tree

18 files changed

+336
-15
lines changed

18 files changed

+336
-15
lines changed

README.adoc

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,34 @@ public class GreeterService extends GreeterGrpc.GreeterImplBase{
186186
}
187187
----
188188

189+
=== Transport Security (TLS)
190+
191+
The transport security can be configured using root certificate and it's private key paths:
192+
193+
[source,yaml]
194+
----
195+
grpc:
196+
security:
197+
cert-chain: classpath:cert/server-cert.pem
198+
private-key: file:../grpc-spring-boot-starter-demo/src/test/resources/cert/server-key.pem
199+
----
200+
201+
The value of both properties is in form supported by https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/io/ResourceEditor.html[ResourceEditor]. +
202+
203+
The client side should be configured accordingly :
204+
205+
[source,java]
206+
----
207+
((NettyChannelBuilder)channelBuilder)
208+
.useTransportSecurity()
209+
.sslContext(GrpcSslContexts.forClient().trustManager(certChain).build());
210+
----
211+
212+
This starter will pull the `io.netty:netty-tcnative-boringssl-static` dependency by default to support SSL. +
213+
If you need another SSL/TLS support, please exclude this dependency and follow https://github.com/grpc/grpc-java/blob/master/SECURITY.md[Security Guide].
214+
215+
[NOTE]
216+
If the more detailed tuning is needed for security setup, please use custom configurer described in <<Custom gRPC Server Configuration>>
189217

190218
=== Custom gRPC Server Configuration
191219

@@ -205,6 +233,10 @@ public class MyGRpcServerBuilderConfigurer extends GRpcServerBuilderConfigurer{
205233
.decompressorRegistry(YOUR DECOMPRESSION REGISTRY)
206234
.useTransportSecurity(YOUR TRANSPORT SECURITY SETTINGS);
207235
((NettyServerBuilder)serverBuilder)// cast to NettyServerBuilder (which is the default server) for further customization
236+
.sslContext(GrpcSslContexts // security fine tuning
237+
.forServer(...)
238+
.trustManager(...)
239+
.build())
208240
.maxConnectionAge(...)
209241
.maxConnectionAgeGrace(...);
210242

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ buildscript {
22
ext {
33
springBoot_1_X_Version = '1.5.13.RELEASE'
44
springBoot_2_X_Version = '2.1.3.RELEASE'
5-
grpcVersion = '1.21.0'
5+
grpcVersion = '1.22.1'
66
}
77
repositories {
88
mavenCentral()

grpc-spring-boot-starter-demo/src/main/protoGen/io/grpc/examples/CalculatorGrpc.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
/**
1111
*/
1212
@javax.annotation.Generated(
13-
value = "by gRPC proto compiler (version 1.21.0)",
13+
value = "by gRPC proto compiler (version 1.22.1)",
1414
comments = "Source: calculator.proto")
1515
public final class CalculatorGrpc {
1616

grpc-spring-boot-starter-demo/src/main/protoGen/io/grpc/examples/GreeterGrpc.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* </pre>
1414
*/
1515
@javax.annotation.Generated(
16-
value = "by gRPC proto compiler (version 1.21.0)",
16+
value = "by gRPC proto compiler (version 1.22.1)",
1717
comments = "Source: greeter.proto")
1818
public final class GreeterGrpc {
1919

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package org.lognet.springboot.grpc;
2+
3+
import org.junit.Test;
4+
import org.junit.runner.RunWith;
5+
import org.lognet.springboot.grpc.demo.DemoApp;
6+
import org.lognet.springboot.rules.ExpectedStartupExceptionWithInspector;
7+
import org.lognet.springboot.rules.SpringRunnerWithGlobalExpectedExceptionInspected;
8+
import org.springframework.beans.factory.BeanCreationException;
9+
import org.springframework.boot.test.context.SpringBootTest;
10+
import org.springframework.test.context.ActiveProfiles;
11+
12+
import java.util.function.Predicate;
13+
14+
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE;
15+
16+
17+
@RunWith(SpringRunnerWithGlobalExpectedExceptionInspected.class)
18+
@SpringBootTest(classes = {DemoApp.class}, webEnvironment = NONE)
19+
@ActiveProfiles("buggy-security")
20+
@ExpectedStartupExceptionWithInspector(GrpcBuggySecuritySettingsTest.ExceptionInspector.class)
21+
public class GrpcBuggySecuritySettingsTest extends GrpcServerTestBase {
22+
@Test
23+
public void contextFails() {
24+
}
25+
26+
public static class ExceptionInspector implements Predicate<Throwable> {
27+
28+
@Override
29+
public boolean test(Throwable throwable) {
30+
Throwable rootCause = throwable;
31+
while (rootCause.getCause() != null && rootCause.getCause() != rootCause) {
32+
rootCause = rootCause.getCause();
33+
}
34+
return rootCause instanceof BeanCreationException;
35+
}
36+
37+
}
38+
39+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.lognet.springboot.grpc;
2+
3+
import org.junit.runner.RunWith;
4+
import org.lognet.springboot.grpc.demo.DemoApp;
5+
import org.springframework.boot.test.context.SpringBootTest;
6+
import org.springframework.test.context.ActiveProfiles;
7+
import org.springframework.test.context.junit4.SpringRunner;
8+
9+
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE;
10+
11+
@RunWith(SpringRunner.class)
12+
@SpringBootTest(classes = {DemoApp.class}, webEnvironment = NONE)
13+
@ActiveProfiles("security")
14+
public class GrpcSecurityTest extends GrpcServerTestBase {
15+
16+
17+
}

grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/GrpcServerTestBase.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import io.grpc.examples.GreeterGrpc;
66
import io.grpc.examples.GreeterOuterClass;
77
import io.grpc.inprocess.InProcessChannelBuilder;
8+
import io.grpc.netty.GrpcSslContexts;
9+
import io.grpc.netty.NettyChannelBuilder;
810
import org.junit.After;
911
import org.junit.Before;
1012
import org.junit.Test;
@@ -13,8 +15,10 @@
1315
import org.springframework.beans.factory.annotation.Autowired;
1416
import org.springframework.beans.factory.annotation.Qualifier;
1517
import org.springframework.context.ApplicationContext;
18+
import org.springframework.core.io.Resource;
1619
import org.springframework.util.StringUtils;
1720

21+
import java.io.IOException;
1822
import java.util.Optional;
1923
import java.util.concurrent.ExecutionException;
2024

@@ -45,11 +49,22 @@ public abstract class GrpcServerTestBase {
4549
protected GRpcServerProperties gRpcServerProperties;
4650

4751
@Before
48-
public final void setupChannels() {
52+
public final void setupChannels() throws IOException {
4953
if(gRpcServerProperties.isEnabled()) {
50-
channel = onChannelBuild(ManagedChannelBuilder.forAddress("localhost",getPort() )
51-
.usePlaintext()
52-
).build();
54+
ManagedChannelBuilder<?> channelBuilder = ManagedChannelBuilder.forAddress("localhost", getPort());
55+
Resource certChain = Optional.ofNullable(gRpcServerProperties.getSecurity())
56+
.map(GRpcServerProperties.SecurityProperties::getCertChain)
57+
.orElse(null);
58+
if(null!= certChain){
59+
((NettyChannelBuilder)channelBuilder)
60+
.useTransportSecurity()
61+
.sslContext(GrpcSslContexts.forClient().trustManager(certChain.getInputStream()).build());
62+
}else{
63+
channelBuilder.usePlaintext();
64+
}
65+
66+
67+
channel = onChannelBuild(channelBuilder).build();
5368
}
5469
if(StringUtils.hasText(gRpcServerProperties.getInProcessServerName())){
5570
inProcChannel = onChannelBuild(
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.lognet.springboot.rules;
2+
3+
import org.junit.AssumptionViolatedException;
4+
import org.junit.runners.model.Statement;
5+
6+
import java.util.function.Predicate;
7+
8+
public class ExpectExceptionWithPredicate extends Statement {
9+
private Predicate<Throwable> inspector;
10+
private Statement next;
11+
12+
public ExpectExceptionWithPredicate(Statement next, Predicate<Throwable> inspector) {
13+
this.next = next;
14+
this.inspector = inspector;
15+
}
16+
17+
@Override
18+
public void evaluate() throws Throwable {
19+
boolean complete = false;
20+
try {
21+
next.evaluate();
22+
complete = true;
23+
} catch (AssumptionViolatedException e) {
24+
throw e;
25+
} catch (Throwable e) {
26+
if (!inspector.test(e)) {
27+
String message = "Unexpected exception, " + inspector.getClass().getName() + " returned false";
28+
throw new Exception(message, e);
29+
}
30+
}
31+
if (complete) {
32+
throw new AssertionError("Expected exception");
33+
}
34+
}
35+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.lognet.springboot.rules;
2+
3+
import java.lang.annotation.Retention;
4+
import java.lang.annotation.Target;
5+
import java.util.function.Predicate;
6+
7+
import static java.lang.annotation.ElementType.METHOD;
8+
import static java.lang.annotation.ElementType.TYPE;
9+
import static java.lang.annotation.RetentionPolicy.RUNTIME;
10+
11+
@Retention(RUNTIME)
12+
@Target({ TYPE, METHOD })
13+
14+
public @interface ExpectedStartupExceptionWithInspector {
15+
16+
Class<? extends Predicate<Throwable>> value();
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.lognet.springboot.rules;
2+
3+
import org.junit.internal.runners.statements.Fail;
4+
import org.junit.runners.model.FrameworkMethod;
5+
import org.junit.runners.model.InitializationError;
6+
import org.junit.runners.model.Statement;
7+
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
8+
9+
import java.util.function.Predicate;
10+
11+
12+
13+
public class SpringRunnerWithGlobalExpectedExceptionInspected extends SpringJUnit4ClassRunner {
14+
private Class<? extends Predicate<Throwable>> expectedExceptionInspector;
15+
16+
public SpringRunnerWithGlobalExpectedExceptionInspected(Class<?> clazz) throws InitializationError {
17+
super(clazz);
18+
ExpectedStartupExceptionWithInspector annotation = clazz.getAnnotation(ExpectedStartupExceptionWithInspector.class);
19+
if (annotation != null) {
20+
expectedExceptionInspector = annotation.value();
21+
} else {
22+
throw new IllegalArgumentException("Missing " + ExpectedStartupExceptionWithInspector.class.getName() + " on " + clazz.getName());
23+
}
24+
}
25+
26+
@Override
27+
protected Statement methodBlock(FrameworkMethod frameworkMethod) {
28+
Statement result = super.methodBlock(frameworkMethod);
29+
try {
30+
return new ExpectExceptionWithPredicate(result, expectedExceptionInspector.newInstance());
31+
} catch (InstantiationException | IllegalAccessException e) {
32+
return new Fail(e);
33+
}
34+
}
35+
36+
}

0 commit comments

Comments
 (0)