Merge branch 'v3.1.x/arios/keystore-grpc' into 3.1.x

This commit is contained in:
spencergibb
2022-08-31 11:52:37 -04:00
22 changed files with 761 additions and 278 deletions

View File

@@ -14,6 +14,7 @@
|spring.cloud.gateway.filter.add-request-header.enabled | `+++true+++` | Enables the add-request-header filter.
|spring.cloud.gateway.filter.add-request-parameter.enabled | `+++true+++` | Enables the add-request-parameter filter.
|spring.cloud.gateway.filter.add-response-header.enabled | `+++true+++` | Enables the add-response-header filter.
|spring.cloud.gateway.filter.json-to-grpc.enabled | `+++true+++` | Enables the JSON to gRPC filter.
|spring.cloud.gateway.filter.circuit-breaker.enabled | `+++true+++` | Enables the circuit-breaker filter.
|spring.cloud.gateway.filter.dedupe-response-header.enabled | `+++true+++` | Enables the dedupe-response-header filter.
|spring.cloud.gateway.filter.fallback-headers.enabled | `+++true+++` | Enables the fallback-headers filter.
@@ -26,7 +27,7 @@
|spring.cloud.gateway.filter.preserve-host-header.enabled | `+++true+++` | Enables the preserve-host-header filter.
|spring.cloud.gateway.filter.redirect-to.enabled | `+++true+++` | Enables the redirect-to filter.
|spring.cloud.gateway.filter.remove-hop-by-hop.headers | |
|spring.cloud.gateway.filter.remove-hop-by-hop.order | `+++0+++` |
|spring.cloud.gateway.filter.remove-hop-by-hop.order | `+++0+++` |
|spring.cloud.gateway.filter.remove-request-header.enabled | `+++true+++` | Enables the remove-request-header filter.
|spring.cloud.gateway.filter.remove-request-parameter.enabled | `+++true+++` | Enables the remove-request-parameter filter.
|spring.cloud.gateway.filter.remove-response-header.enabled | `+++true+++` | Enables the remove-response-header filter.
@@ -42,16 +43,16 @@
|spring.cloud.gateway.filter.rewrite-path.enabled | `+++true+++` | Enables the rewrite-path filter.
|spring.cloud.gateway.filter.rewrite-response-header.enabled | `+++true+++` | Enables the rewrite-response-header filter.
|spring.cloud.gateway.filter.save-session.enabled | `+++true+++` | Enables the save-session filter.
|spring.cloud.gateway.filter.secure-headers.content-security-policy | `+++default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'+++` |
|spring.cloud.gateway.filter.secure-headers.content-type-options | `+++nosniff+++` |
|spring.cloud.gateway.filter.secure-headers.content-security-policy | `+++default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'+++` |
|spring.cloud.gateway.filter.secure-headers.content-type-options | `+++nosniff+++` |
|spring.cloud.gateway.filter.secure-headers.disable | |
|spring.cloud.gateway.filter.secure-headers.download-options | `+++noopen+++` |
|spring.cloud.gateway.filter.secure-headers.download-options | `+++noopen+++` |
|spring.cloud.gateway.filter.secure-headers.enabled | `+++true+++` | Enables the secure-headers filter.
|spring.cloud.gateway.filter.secure-headers.frame-options | `+++DENY+++` |
|spring.cloud.gateway.filter.secure-headers.permitted-cross-domain-policies | `+++none+++` |
|spring.cloud.gateway.filter.secure-headers.referrer-policy | `+++no-referrer+++` |
|spring.cloud.gateway.filter.secure-headers.strict-transport-security | `+++max-age=631138519+++` |
|spring.cloud.gateway.filter.secure-headers.xss-protection-header | `+++1 ; mode=block+++` |
|spring.cloud.gateway.filter.secure-headers.frame-options | `+++DENY+++` |
|spring.cloud.gateway.filter.secure-headers.permitted-cross-domain-policies | `+++none+++` |
|spring.cloud.gateway.filter.secure-headers.referrer-policy | `+++no-referrer+++` |
|spring.cloud.gateway.filter.secure-headers.strict-transport-security | `+++max-age=631138519+++` |
|spring.cloud.gateway.filter.secure-headers.xss-protection-header | `+++1 ; mode=block+++` |
|spring.cloud.gateway.filter.set-path.enabled | `+++true+++` | Enables the set-path filter.
|spring.cloud.gateway.filter.set-request-header.enabled | `+++true+++` | Enables the set-request-header filter.
|spring.cloud.gateway.filter.set-request-host-header.enabled | `+++true+++` | Enables the set-request-host-header filter.
@@ -106,7 +107,7 @@
|spring.cloud.gateway.httpclient.websocket.proxy-ping | `+++true+++` | Proxy ping frames to downstream services, defaults to true.
|spring.cloud.gateway.httpclient.wiretap | `+++false+++` | Enables wiretap debugging for Netty HttpClient.
|spring.cloud.gateway.httpserver.wiretap | `+++false+++` | Enables wiretap debugging for Netty HttpServer.
|spring.cloud.gateway.loadbalancer.use404 | `+++false+++` |
|spring.cloud.gateway.loadbalancer.use404 | `+++false+++` |
|spring.cloud.gateway.metrics.enabled | `+++false+++` | Enables the collection of metrics data.
|spring.cloud.gateway.metrics.prefix | `+++spring.cloud.gateway+++` | The prefix of all metrics emitted by gateway.
|spring.cloud.gateway.metrics.tags | | Tags map that added to metrics.

View File

@@ -1894,7 +1894,7 @@ spring:
When a request is made through the gateway to `/json/hello` the request will be transformed using the definition provided in `hello.proto`, sent to `com.example.grpcserver.hello.HelloService/hello`, and transform the response back to JSON.
By default, it will create a `NettyChannel` using the default `TrustManagerFactory`. However, this `TrustManager` can be customized by creating a bean of type `GRPCSSLContext`:
By default, it will create a `NettyChannel` using the default `TrustManagerFactory`. However, this `TrustManager` can be customized by creating a bean of type `GrpcSslConfigurer`:
[source,java]
----

View File

@@ -56,7 +56,7 @@
<junit-pioneer.version>1.0.0</junit-pioneer.version>
<spring-cloud-circuitbreaker.version>2.1.4-SNAPSHOT</spring-cloud-circuitbreaker.version>
<spring-cloud-commons.version>3.1.4-SNAPSHOT</spring-cloud-commons.version>
<testcontainers.version>1.16.3</testcontainers.version>
<testcontainers.version>1.17.2</testcontainers.version>
</properties>
<dependencyManagement>

View File

@@ -30,9 +30,13 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<artifactId>grpc-netty</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>

View File

@@ -18,31 +18,24 @@ package org.springframework.cloud.gateway.tests.grpc;
import java.io.File;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import io.grpc.Grpc;
import io.grpc.Server;
import io.grpc.ServerCredentials;
import io.grpc.TlsServerCredentials;
import io.grpc.stub.StreamObserver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.gateway.config.GRPCSSLContext;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import org.springframework.util.SocketUtils;
/**
* @author Alberto C. Ríos
@@ -51,59 +44,39 @@ import org.springframework.util.SocketUtils;
@EnableAutoConfiguration
public class GRPCApplication {
private static final int GRPC_SERVER_PORT = SocketUtils.findAvailableTcpPort();
public static void main(String[] args) {
SpringApplication.run(GRPCApplication.class, args);
}
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes().route("json-grpc", r -> r.path("/json/hello").filters(f -> {
String protoDescriptor = "file:src/main/proto/hello.pb";
String protoFile = "file:src/main/proto/hello.proto";
String service = "HelloService";
String method = "hello";
return f.jsonToGRPC(protoDescriptor, protoFile, service, method);
}).uri("https://localhost:" + GRPC_SERVER_PORT))
.route("grpc", r -> r.predicate(p -> true).uri("https://localhost:" + GRPC_SERVER_PORT)).build();
}
@Bean
public GRPCSSLContext sslContext() throws SSLException {
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
} };
return new GRPCSSLContext(trustAllCerts[0]);
}
@Component
static class GRPCServer implements ApplicationRunner {
private static final Logger log = LoggerFactory.getLogger(GRPCServer.class);
private final Environment environment;
private Server server;
GRPCServer(Environment environment) {
this.environment = environment;
}
@Override
public void run(ApplicationArguments args) throws Exception {
final GRPCServer server = new GRPCServer();
final GRPCServer server = new GRPCServer(environment);
server.start();
}
private void start() throws Exception {
/* The port on which the server should run */
private void start() throws IOException {
Integer serverPort = environment.getProperty("local.server.port", Integer.class);
int grpcPort = serverPort + 1;
/*
* The port on which the server should run. We run
*/
ServerCredentials creds = createServerCredentials();
server = Grpc.newServerBuilderForPort(GRPC_SERVER_PORT, creds).addService(new HelloService()).build()
.start();
server = Grpc.newServerBuilderForPort(grpcPort, creds).addService(new HelloService()).build().start();
System.out.println("Starting gRPC server in port " + GRPC_SERVER_PORT);
log.info("Starting gRPC server in port " + grpcPort);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
@@ -116,8 +89,9 @@ public class GRPCApplication {
}
private ServerCredentials createServerCredentials() throws IOException {
File certChain = new ClassPathResource("public.cert").getFile();
File privateKey = new ClassPathResource("private.key").getFile();
File certChain = new ClassPathResource("certificate.pem").getFile();
return TlsServerCredentials.create(certChain, privateKey);
}
@@ -132,8 +106,8 @@ public class GRPCApplication {
@Override
public void hello(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
String greeting = "Hello, " + request.getFirstName() + " " + request.getLastName();
System.out.println("Sending response: " + greeting);
String greeting = String.format("Hello, %s %s", request.getFirstName(), request.getLastName());
log.info("Sending response: " + greeting);
HelloResponse response = HelloResponse.newBuilder().setGreeting(greeting).build();

View File

@@ -1,21 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIEIon96DANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdV
bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD
VQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3du
MB4XDTIxMDkyNzE2NDQwNVoXDTMxMDkyNTE2NDQwNVowbDEQMA4GA1UEBhMHVW5r
bm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UE
ChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIwevGHY30YdoUdCSB/5q7/F
c0KHetdjb71G2u6vFdeNvSwMpCV8Z1JznOJ/1zuY+0Z105QCPM7fi1ACi+tqxDR+
L7yjUHhUEMTiGgCHcYJIZZCYfWS3BQXVxgORXhDv7RduCUaCLnkaFY++iPMTUy0C
VxIkplIEhAmqcikIgWaa5ZjBkegKgahlPQLKlfD4Rz/kq2P+LLYFsHNNdKfWv6XQ
u4LMw7ZEAJtfdpaMTzmtQipbTt6Dh87vIa0CIVnCPdlQ3o/5WeaxEA1pnfOLas07
1VdHih2nC5vHhQcTPQDfa+uwzQvzHrchjuvMUUZaCYJzuT0G6nbGBba54EBT7yUC
AwEAAaMhMB8wHQYDVR0OBBYEFKOHmfytP5ab2C4iFHlSklu6tCcuMA0GCSqGSIb3
DQEBCwUAA4IBAQAdgWwdOtRbI796Z22weTBc0/tM8kLc6G0raNb08WyZMPZVki04
jPh73pPQCgYeI/pq5JqH46KgvehmygTzpWDAFIllW0kgABVw3Nu6duV+blt1JG8T
lWP7t5A+qDXgPDm3I5diii7O1YlLB3I37XiBdEV/+2WmF1VGQ7uBWAv+uotQeuW2
JvHOr4ICOiW45TzRYtAbzWukSYKg/A7lwBs7HE9KVomUxNrkD+7+ugRuy/31pyen
pHsEJQpx5juFRE222B6GXmX0w9xLIOapytl4EoPUx3K8Ecc+yI2q00UUC43x0v28
c05YqwRZ5vp+jUnRxkxaz85YdArfR7QFYWtO
-----END CERTIFICATE-----

View File

@@ -1,32 +1,32 @@
Bag Attributes
friendlyName: mykey
localKeyID: 54 69 6D 65 20 31 36 33 32 38 32 34 38 37 31 33 33 35
friendlyName: bootapp
localKeyID: 54 69 6D 65 20 31 36 35 39 39 37 34 34 38 34 30 33 30
Key Attributes: <No Attributes>
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCMHrxh2N9GHaFH
Qkgf+au/xXNCh3rXY2+9RtrurxXXjb0sDKQlfGdSc5zif9c7mPtGddOUAjzO34tQ
AovrasQ0fi+8o1B4VBDE4hoAh3GCSGWQmH1ktwUF1cYDkV4Q7+0XbglGgi55GhWP
vojzE1MtAlcSJKZSBIQJqnIpCIFmmuWYwZHoCoGoZT0CypXw+Ec/5Ktj/iy2BbBz
TXSn1r+l0LuCzMO2RACbX3aWjE85rUIqW07eg4fO7yGtAiFZwj3ZUN6P+VnmsRAN
aZ3zi2rNO9VXR4odpwubx4UHEz0A32vrsM0L8x63IY7rzFFGWgmCc7k9Bup2xgW2
ueBAU+8lAgMBAAECggEAGu2xQJDAYCZDn4FCgTqnYkSdIRUOa6SFjfe3DZYCeZmY
2IVZaobdCICFjxYIlECTUfhFADXp38wgZvEGWOj86iWyIOu2BFoLmvrlCmL9Uo99
TWuw9ZEi2vs5gegHDvQ9OXqBN9a+/bEgoa55fVWib4z6lNcMS8joYz8pj28+ByzE
LW0/3T3p6beM2fUcCJWn3d2M3wUgSuXmcdjVXJSkhEwKTVcc4vTTcOeF6xb2VZ/g
Pozv/39G1qZ+QtM58yBiqnJ1Z2gtAk71l/1ztQa3uY22gzw8Kj5dmqcHgdiN5DWI
bNE+k5Q93FzUmZNYPzmY6YVkdEzaNMtmi96sdtEu5QKBgQD36YdGXUyoRXeNDRuc
yMXr6j/9/ewii+byHhFoUvjLuXWIQ06V+nOtYqohg/zgGIiC5LQ8EB0uFZDMhbDf
kSwtoXbpUDLYD2OPgIyLaPqHiYQ9BampbUz5vHlfYLr0vB+Xp5r22Eb6nDQRRtb5
EXHoYgAokgpYdeTIcdKRcB/2DwKBgQCQsPePeu7PagM9vYEo4zfbFxfY3Qkr4lOQ
BCZ/tgsS0b0jAAxpfUH0/3O5oXizmB/5K7vgKqGnTuBWJJ/hdCWq27FkKxJ+8ejU
8V9TFd5VQ89VeB5OekZwPks8vftwzW4L82ZRW5hvyQB0jPR+lwqjrNqds+xxH7i+
c+RFx15biwKBgQCgkGGK0zao7YUGl+zAWNDHgQo9KM5deZr0SUEg/kwhNlbHEEC/
plxxeauS1XdcdMdFb3bER/N+O31y2Uu7IL0qOJ9ZcRXdFep3sNxWFoHcctZw51AB
accnIEjD21R62bTkditJoL4n5i9a2TS2T/QkfAR6QkvtCz5IDGBCzgoFRQKBgC51
VBfy3gklPgMt/PHW+1FSuep9FnvLwQ8F9iKdnjKdu8AoPNQGTw5Ok6bwDOSFnQaR
n1Kb/anN7sRaICfw9kNFJVFHbznpjNwK4JO5+tif3EvSNND3+/QAXIIVck3G+GXH
8nt/EJQcExRZSgv3jYf+cXeflPTBvb0RUyOAn3B/AoGAM9aUi2dg3XjlDkZahQlY
P5QNIr9BY25Ordga3GLwfR6rE+jiWTeTmreXBSJ7nvcaEQUNyFMX9n3v/QI84etk
lMJkZ4o2TgzRnCsFJoBV36Ihsa6B5uXVWAvMRLKvwKpptKXfO0TnhUg7oKtN4t3a
/FzAOS2Eu1PFP27z74gAMKY=
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQClCZtSgRjM2uYM
O70+acD4rhwcc8ND23SwoKTNLPH6s1K2Pi5MZqF4Rnx+REJwerQrkgfMnsQ0QHcb
NjI9dhSwHw6aPigAGJQEzR8whDu2PkR57iBmySr3GLtpR+WkVGo/xAK92ozKt9aW
Saxm0miygYI8mgzrNpbaQAu6sZcS3RHkPYWw/izYVZLBB6IEpAJJrUqjzlu53TyN
X4VC4FWddItjOCiyrHe2+A+tFLNK3IZnFZBqoMcYR6/Rq+zsXx8ynCggkUwTFaPd
IVyzSYtooE2oq+aSjkJzKvoPA7BObLnrW+5Dklwj1iPtLhfuCflz2oa54/Hpl60R
Ke6y6/t5AgMBAAECggEAQvJ/sFswDUGq2kGNhfjuT7KJMr1+81Ldphy0XYqi6li9
77GPpGxpidnF/I6CCRCtb5NAWK/61VtlNYOpo6b3w24FxWn7XfaabMwsn8i0VDw0
GOYQ/MEUDcJZm96PeDbKFu3TUuKKBF3IzZQ7PEaUM/03MJApN20gio46c9RAfWTK
hN9jX9YkBpkSWPfx9RA9BOIXRrRMTXhidsVPnNxhhM7OAGZ1LhggDPaeiCIbVx6l
yMsyCkNvqdQbKu0hFcVyeQsGjovCC1Ye71F3em3ACguqDu+VAy3flpTbcDVf2D7T
zP2T5e5OEJ2hRTGdnEVSsXxWOWnq6Gt6ydNEe2AWVQKBgQC5HcQAUTnvIMaTez44
7SBfELNY1AzF24dha24JFIGY78QNRZi+4Agsvxjkx4fKSd4DfmPsCqentmEkEcDr
w6fH0CAFm/rrkzLHmesBCC8byl4WtfMW5i8h1yzOeQbYpx3MGIu5xhkE92gbNj+o
ioM7nkoBvloU0odiYTVnoNYnFwKBgQDkO5ldA8Vl3+BWhmhOtPu+1EKssWry5rdO
m6IiZ1uB6uG4bhAj5dSVVgDRX6pcuppMVZqIbPpdkS7/i0LVwohT7EmpMMJ5bezz
EhG1z6AvZDcGMDC/PPMp7SLjJviYZDaTWLekKwY6yV1T9gtOY70jjWkUcfN1IHpA
renHNNyL7wKBgQCEYvIqW+y3xFPfY1MzePoeop3wl+3ujjo6hI7z9XNdgZNO/ofn
cebGwX+3Fa9aDwu0qe4h/9i4y2ibWAsFUS6ran+MI2oGkYXOU5hKa6TtFgPF8CfC
J6prZCxKGSm5RYK81I0Qtchs0dblJx3NlgmWWHSK3Kwlmg5yYBzGWLLuzwKBgDzg
rNrDq76txcAun9oGqnPPWG2J8XYTFmgQWWIF4cG4rjasnP+GSXr/8r3mX6HWYFvm
JY8oSmv00u107wHnnseL6mYHzIfpS1/WvQSa+iZJ++dZqVcJYe8YAstGVN8JNAl/
i5RtqX66wXso0QE613OJP7MlZgQjApkICqiJMB7fAoGAVFdv/Nphcju7MCkXGuQe
amk7Z6Q2PjMWvLwiyB39PrIzvTOdaQyQk7oYdnAGFJbkmi7mmGiet5uvcE9OjNRb
+XeXo2LEZ2QWJEnHi9uCXSQF3eeBlJ2wGUsELPTHglWIUTpi5iDB2hHzczPzO8GY
qPb+ckCl8mTsHRKQQse4Bm0=
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC1zCCAb+gAwIBAgIIEsL9TgSEch8wDQYJKoZIhvcNAQELBQAwGjEYMBYGA1UE
AxMPbXktYm9vdC1hcHAub3JnMB4XDTIyMDgwODE2MDEyNFoXDTMyMDgwNTE2MDEy
NFowGjEYMBYGA1UEAxMPbXktYm9vdC1hcHAub3JnMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEApQmbUoEYzNrmDDu9PmnA+K4cHHPDQ9t0sKCkzSzx+rNS
tj4uTGaheEZ8fkRCcHq0K5IHzJ7ENEB3GzYyPXYUsB8Omj4oABiUBM0fMIQ7tj5E
ee4gZskq9xi7aUflpFRqP8QCvdqMyrfWlkmsZtJosoGCPJoM6zaW2kALurGXEt0R
5D2FsP4s2FWSwQeiBKQCSa1Ko85bud08jV+FQuBVnXSLYzgosqx3tvgPrRSzStyG
ZxWQaqDHGEev0avs7F8fMpwoIJFMExWj3SFcs0mLaKBNqKvmko5Ccyr6DwOwTmy5
61vuQ5JcI9Yj7S4X7gn5c9qGuePx6ZetESnusuv7eQIDAQABoyEwHzAdBgNVHQ4E
FgQUf72EgsQASMCzSGwrFDaKnBoufm8wDQYJKoZIhvcNAQELBQADggEBAAHzJkp/
q+U0ki+JaFaUBYg85h+yJbi262hmFjLYDQEcz2tvAw93X9ytOzVtmo2H9AgsTJYA
z4UbJHfnTTa1KAHDHhzL/Adh/s2OZ7Y2kazLrHArFteKtGUo815JDL935JpiRBWc
Qn1WJJ6EFb+ZRMG7oROQ3LgvrradH8G51qxvSbH/fpmEFDTSHJNIG8tXcIA3fJkI
dsVJLygrXLzZQo5xI8rwMDX9aujRFU4Dgit58xquCkuzob/BonN7Es1Hp3beQjro
gddts4x5wWyiFun7uYrbPVhGMJNgwRKB5e3QQA6vYDyeQqvIy6n1lnDNIxvaOfPW
Je5gI59jCbxHs5g=
-----END CERTIFICATE-----

View File

@@ -23,8 +23,8 @@ import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import io.grpc.ManagedChannel;
import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
import io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.NettyChannelBuilder;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -32,7 +32,7 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.annotation.DirtiesContext;
import static io.grpc.netty.shaded.io.grpc.netty.NegotiationType.TLS;
import static io.grpc.netty.NegotiationType.TLS;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
/**
@@ -47,7 +47,7 @@ public class GRPCApplicationTests {
@Test
public void gRPCUnaryCalShouldReturnResponse() throws SSLException {
ManagedChannel channel = createSecuredChannel(port);
ManagedChannel channel = createSecuredChannel(port + 1);
final HelloResponse response = HelloServiceGrpc.newBlockingStub(channel)
.hello(HelloRequest.newBuilder().setFirstName("Sir").setLastName("FromClient").build());

View File

@@ -19,6 +19,11 @@ package org.springframework.cloud.gateway.tests.grpc;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import javax.net.ssl.SSLContext;
@@ -39,13 +44,19 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.client.RestTemplate;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
/**
* @author Alberto C. Ríos
* @author Abel Salgado Romero
*/
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class JsonToGrpcApplicationTests {
@@ -62,7 +73,14 @@ public class JsonToGrpcApplicationTests {
@Test
public void shouldConvertFromJSONToGRPC() {
String response = restTemplate.postForEntity("https://localhost:" + port + "/json/hello",
// Since GRPC server and GW run in same instance and don't know server port until
// test starts,
// we need to configure route dynamically using the actuator endpoint.
final RouteConfigurer configurer = new RouteConfigurer(port);
configurer.addRoute(port + 1, "/json/hello",
"JsonToGrpc=file:src/main/proto/hello.pb,file:src/main/proto/hello.proto,HelloService,hello");
String response = restTemplate.postForEntity("https://localhost:" + this.port + "/json/hello",
"{\"firstName\":\"Duff\", \"lastName\":\"McKagan\"}", String.class).getBody();
Assertions.assertThat(response).isNotNull();
@@ -94,4 +112,46 @@ public class JsonToGrpcApplicationTests {
return new RestTemplate(requestFactory);
}
class RouteConfigurer {
private final WebTestClient actuatorWebClient;
private final int actuatorPort;
RouteConfigurer(int actuatorPort) {
this.actuatorPort = actuatorPort;
this.actuatorWebClient = WebTestClient.bindToServer().baseUrl("http://localhost:" + actuatorPort).build();
}
public void addRoute(int uriPort, String path, String filter) {
final String routeId = "test-route-" + UUID.randomUUID();
Map<String, Object> route = new HashMap<>();
route.put("id", routeId);
route.put("uri", "http://localhost:" + uriPort);
route.put("predicates", Collections.singletonList("Path=" + path));
route.put("filters", Arrays.asList(filter));
ResponseEntity<String> exchange = restTemplate.exchange(url("/actuator/gateway/routes/" + routeId),
HttpMethod.POST, new HttpEntity<>(route), String.class);
assert exchange.getStatusCode() == HttpStatus.CREATED;
refreshRoutes();
}
private void refreshRoutes() {
ResponseEntity<String> exchange = restTemplate.exchange(url("/actuator/gateway/refresh"), HttpMethod.POST,
new HttpEntity<>(""), String.class);
assert exchange.getStatusCode() == HttpStatus.OK;
}
private String url(String context) {
return String.format("https://localhost:%s%s", this.actuatorPort, context);
}
}
}

View File

@@ -2,11 +2,22 @@ server:
http2:
enabled: true
ssl:
key-store-type: PKCS12
key-store: classpath:keystore.p12
key-store-type: pkcs12
key-store-password: password
key-password: password
enabled: true
key-alias: bootapp
management:
endpoint:
health:
show-details: when_authorized
gateway:
enabled: true
endpoints:
web:
exposure:
include: "*"
spring:
cloud:
gateway:
@@ -15,11 +26,10 @@ spring:
httpclient:
wiretap: true
ssl:
key-store-type: PKCS12
key-store: classpath:keystore.p12
key-store-password: password
key-password: password
use-insecure-trust-manager: true
# Requires faking domain name (modifying /etc/hosts)
# trustedX509Certificates:
# - classpath:public.cert
logging:
level:

View File

@@ -94,7 +94,7 @@
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<artifactId>grpc-netty</artifactId>
<optional>true</optional>
<version>${grpc.version}</version>
</dependency>

View File

@@ -0,0 +1,139 @@
/*
* Copyright 2013-2020 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.cloud.gateway.config;
import java.io.IOException;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchProviderException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManagerFactory;
import io.netty.handler.ssl.SslContextBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.ResourceUtils;
/**
* Base class to configure SSL for component T. Returns an instance S with the resulting
* configuration (can be the same as T).
*
* @author Abel Salgado Romero
*/
public abstract class AbstractSslConfigurer<T, S> {
protected final Log logger = LogFactory.getLog(this.getClass());
private final HttpClientProperties.Ssl ssl;
protected AbstractSslConfigurer(HttpClientProperties.Ssl sslProperties) {
this.ssl = sslProperties;
}
abstract public S configureSsl(T client) throws SSLException;
protected HttpClientProperties.Ssl getSslProperties() {
return ssl;
}
protected X509Certificate[] getTrustedX509CertificatesForTrustManager() {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
ArrayList<Certificate> allCerts = new ArrayList<>();
for (String trustedCert : ssl.getTrustedX509Certificates()) {
try {
URL url = ResourceUtils.getURL(trustedCert);
Collection<? extends Certificate> certs = certificateFactory.generateCertificates(url.openStream());
allCerts.addAll(certs);
}
catch (IOException e) {
throw new RuntimeException("Could not load certificate '" + trustedCert + "'", e);
}
}
return allCerts.toArray(new X509Certificate[allCerts.size()]);
}
catch (CertificateException e1) {
throw new RuntimeException("Could not load CertificateFactory X.509", e1);
}
}
protected KeyManagerFactory getKeyManagerFactory() {
try {
if (ssl.getKeyStore() != null && ssl.getKeyStore().length() > 0) {
KeyManagerFactory keyManagerFactory = KeyManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
char[] keyPassword = ssl.getKeyPassword() != null ? ssl.getKeyPassword().toCharArray() : null;
if (keyPassword == null && ssl.getKeyStorePassword() != null) {
keyPassword = ssl.getKeyStorePassword().toCharArray();
}
keyManagerFactory.init(this.createKeyStore(), keyPassword);
return keyManagerFactory;
}
return null;
}
catch (Exception e) {
throw new IllegalStateException(e);
}
}
protected KeyStore createKeyStore() {
try {
KeyStore store = ssl.getKeyStoreProvider() != null
? KeyStore.getInstance(ssl.getKeyStoreType(), ssl.getKeyStoreProvider())
: KeyStore.getInstance(ssl.getKeyStoreType());
try {
URL url = ResourceUtils.getURL(ssl.getKeyStore());
store.load(url.openStream(),
ssl.getKeyStorePassword() != null ? ssl.getKeyStorePassword().toCharArray() : null);
}
catch (Exception e) {
throw new RuntimeException("Could not load key store ' " + ssl.getKeyStore() + "'", e);
}
return store;
}
catch (KeyStoreException | NoSuchProviderException e) {
throw new RuntimeException("Could not load KeyStore for given type and provider", e);
}
}
protected void setTrustManager(SslContextBuilder sslContextBuilder, X509Certificate... trustedX509Certificates) {
sslContextBuilder.trustManager(trustedX509Certificates);
}
protected void setTrustManager(SslContextBuilder sslContextBuilder, TrustManagerFactory factory) {
sslContextBuilder.trustManager(factory);
}
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright 2013-2020 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.cloud.gateway.config;
import javax.net.ssl.TrustManager;
/**
* @author Alberto C. Ríos
*/
public class GRPCSSLContext {
private final TrustManager trustManager;
public GRPCSSLContext(TrustManager trustManager) {
this.trustManager = trustManager;
}
public TrustManager getTrustManager() {
return trustManager;
}
}

View File

@@ -25,6 +25,7 @@ import java.util.function.Supplier;
import javax.net.ssl.TrustManagerFactory;
import io.grpc.Channel;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Flux;
@@ -302,22 +303,23 @@ public class GatewayAutoConfiguration {
@Bean
@ConditionalOnEnabledFilter
@ConditionalOnProperty(name = "server.http2.enabled", matchIfMissing = true)
@ConditionalOnClass(name = "io.grpc.Channel")
public JsonToGrpcGatewayFilterFactory jsonToGRPCFilterFactory(GRPCSSLContext gRPCSSLContext,
@ConditionalOnClass(Channel.class)
public JsonToGrpcGatewayFilterFactory jsonToGRPCFilterFactory(GrpcSslConfigurer gRPCSSLContext,
ResourceLoader resourceLoader) {
return new JsonToGrpcGatewayFilterFactory(gRPCSSLContext, resourceLoader);
}
@Bean
@ConditionalOnEnabledFilter(JsonToGrpcGatewayFilterFactory.class)
@ConditionalOnMissingBean(GRPCSSLContext.class)
@ConditionalOnMissingBean(GrpcSslConfigurer.class)
@ConditionalOnClass(name = "io.grpc.Channel")
public GRPCSSLContext gRPCSSLContext() throws KeyStoreException, NoSuchAlgorithmException {
public GrpcSslConfigurer grpcSslConfigurer(HttpClientProperties properties)
throws KeyStoreException, NoSuchAlgorithmException {
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(KeyStore.getInstance(KeyStore.getDefaultType()));
return new GRPCSSLContext(trustManagerFactory.getTrustManagers()[0]);
return new GrpcSslConfigurer(properties.getSsl());
}
@Bean
@@ -683,11 +685,19 @@ public class GatewayAutoConfiguration {
};
}
@Bean
public HttpClientSslConfigurer httpClientSslConfigurer(ServerProperties serverProperties,
HttpClientProperties httpClientProperties) {
return new HttpClientSslConfigurer(httpClientProperties.getSsl(), serverProperties) {
};
}
@Bean
@ConditionalOnMissingBean({ HttpClient.class, HttpClientFactory.class })
public HttpClientFactory gatewayHttpClientFactory(HttpClientProperties properties,
ServerProperties serverProperties, List<HttpClientCustomizer> customizers) {
return new HttpClientFactory(properties, serverProperties, customizers);
ServerProperties serverProperties, List<HttpClientCustomizer> customizers,
HttpClientSslConfigurer sslConfigurer) {
return new HttpClientFactory(properties, serverProperties, sslConfigurer, customizers);
}
@Bean

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2013-2020 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.cloud.gateway.config;
import javax.net.ssl.SSLException;
import io.grpc.ManagedChannel;
import io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.NettyChannelBuilder;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
/**
* @author Alberto C. Ríos
*/
public class GrpcSslConfigurer extends AbstractSslConfigurer<NettyChannelBuilder, ManagedChannel> {
public GrpcSslConfigurer(HttpClientProperties.Ssl sslProperties) {
super(sslProperties);
}
@Override
public ManagedChannel configureSsl(NettyChannelBuilder NettyChannelBuilder) throws SSLException {
return NettyChannelBuilder.useTransportSecurity().sslContext(getSslContext()).build();
}
private SslContext getSslContext() throws SSLException {
final SslContextBuilder sslContextBuilder = GrpcSslContexts.forClient();
final HttpClientProperties.Ssl ssl = getSslProperties();
boolean useInsecureTrustManager = ssl.isUseInsecureTrustManager();
if (useInsecureTrustManager) {
sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE.getTrustManagers()[0]);
}
if (!useInsecureTrustManager && ssl.getTrustedX509Certificates().size() > 0) {
sslContextBuilder.trustManager(getTrustedX509CertificatesForTrustManager());
}
return sslContextBuilder.keyManager(getKeyManagerFactory()).build();
}
}

View File

@@ -69,12 +69,23 @@ public class HttpClientFactory extends AbstractFactoryBean<HttpClient> {
protected final ServerProperties serverProperties;
protected final HttpClientSslConfigurer sslConfigurer;
protected final List<HttpClientCustomizer> customizers;
public HttpClientFactory(HttpClientProperties properties, ServerProperties serverProperties,
List<HttpClientCustomizer> customizers) {
this.properties = properties;
this.serverProperties = serverProperties;
this.sslConfigurer = null;
this.customizers = customizers;
}
public HttpClientFactory(HttpClientProperties properties, ServerProperties serverProperties,
HttpClientSslConfigurer sslConfigurer, List<HttpClientCustomizer> customizers) {
this.properties = properties;
this.serverProperties = serverProperties;
this.sslConfigurer = sslConfigurer;
this.customizers = customizers;
}
@@ -117,17 +128,11 @@ public class HttpClientFactory extends AbstractFactoryBean<HttpClient> {
return httpClient;
}
private HttpClient applyCustomizers(HttpClient httpClient) {
if (!CollectionUtils.isEmpty(customizers)) {
customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
for (HttpClientCustomizer customizer : customizers) {
httpClient = customizer.customize(httpClient);
}
}
return httpClient;
}
protected HttpClient configureSsl(HttpClient httpClient) {
if (sslConfigurer != null) {
return sslConfigurer.configureSsl(httpClient);
}
HttpClientProperties.Ssl ssl = properties.getSsl();
if ((ssl.getKeyStore() != null && ssl.getKeyStore().length() > 0)
|| getTrustedX509CertificatesForTrustManager().length > 0 || ssl.isUseInsecureTrustManager()) {
@@ -139,6 +144,7 @@ public class HttpClientFactory extends AbstractFactoryBean<HttpClient> {
return httpClient;
}
@Deprecated
protected void configureSslContext(HttpClientProperties.Ssl ssl, SslProvider.SslContextSpec sslContextSpec) {
SslProvider.ProtocolSslContextSpec clientSslContext = (serverProperties.getHttp2().isEnabled())
? Http2SslContextSpec.forClient() : Http11SslContextSpec.forClient();
@@ -164,6 +170,16 @@ public class HttpClientFactory extends AbstractFactoryBean<HttpClient> {
.closeNotifyReadTimeout(ssl.getCloseNotifyReadTimeout());
}
private HttpClient applyCustomizers(HttpClient httpClient) {
if (!CollectionUtils.isEmpty(customizers)) {
customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
for (HttpClientCustomizer customizer : customizers) {
httpClient = customizer.customize(httpClient);
}
}
return httpClient;
}
protected HttpClient configureProxy(HttpClient httpClient) {
// configure proxy if proxy host is set.
if (StringUtils.hasText(properties.getProxy().getHost())) {
@@ -176,6 +192,88 @@ public class HttpClientFactory extends AbstractFactoryBean<HttpClient> {
return httpClient;
}
@Deprecated
protected X509Certificate[] getTrustedX509CertificatesForTrustManager() {
HttpClientProperties.Ssl ssl = properties.getSsl();
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
ArrayList<Certificate> allCerts = new ArrayList<>();
for (String trustedCert : ssl.getTrustedX509Certificates()) {
try {
URL url = ResourceUtils.getURL(trustedCert);
Collection<? extends Certificate> certs = certificateFactory.generateCertificates(url.openStream());
allCerts.addAll(certs);
}
catch (IOException e) {
throw new RuntimeException("Could not load certificate '" + trustedCert + "'", e);
}
}
return allCerts.toArray(new X509Certificate[allCerts.size()]);
}
catch (CertificateException e1) {
throw new RuntimeException("Could not load CertificateFactory X.509", e1);
}
}
@Deprecated
protected KeyManagerFactory getKeyManagerFactory() {
HttpClientProperties.Ssl ssl = properties.getSsl();
try {
if (ssl.getKeyStore() != null && ssl.getKeyStore().length() > 0) {
KeyManagerFactory keyManagerFactory = KeyManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
char[] keyPassword = ssl.getKeyPassword() != null ? ssl.getKeyPassword().toCharArray() : null;
if (keyPassword == null && ssl.getKeyStorePassword() != null) {
keyPassword = ssl.getKeyStorePassword().toCharArray();
}
keyManagerFactory.init(this.createKeyStore(), keyPassword);
return keyManagerFactory;
}
return null;
}
catch (Exception e) {
throw new IllegalStateException(e);
}
}
@Deprecated
protected KeyStore createKeyStore() {
HttpClientProperties.Ssl ssl = properties.getSsl();
try {
KeyStore store = ssl.getKeyStoreProvider() != null
? KeyStore.getInstance(ssl.getKeyStoreType(), ssl.getKeyStoreProvider())
: KeyStore.getInstance(ssl.getKeyStoreType());
try {
URL url = ResourceUtils.getURL(ssl.getKeyStore());
store.load(url.openStream(),
ssl.getKeyStorePassword() != null ? ssl.getKeyStorePassword().toCharArray() : null);
}
catch (Exception e) {
throw new RuntimeException("Could not load key store ' " + ssl.getKeyStore() + "'", e);
}
return store;
}
catch (KeyStoreException | NoSuchProviderException e) {
throw new RuntimeException("Could not load KeyStore for given type and provider", e);
}
}
@Deprecated
protected void setTrustManager(SslContextBuilder sslContextBuilder, X509Certificate... trustedX509Certificates) {
sslContextBuilder.trustManager(trustedX509Certificates);
}
@Deprecated
protected void setTrustManager(SslContextBuilder sslContextBuilder, TrustManagerFactory factory) {
sslContextBuilder.trustManager(factory);
}
protected ProxyProvider.Builder configureProxyProvider(HttpClientProperties.Proxy proxy,
ProxyProvider.TypeSpec proxySpec) {
ProxyProvider.Builder builder = proxySpec.type(proxy.getType()).host(proxy.getHost());
@@ -201,83 +299,6 @@ public class HttpClientFactory extends AbstractFactoryBean<HttpClient> {
return spec;
}
protected X509Certificate[] getTrustedX509CertificatesForTrustManager() {
HttpClientProperties.Ssl ssl = properties.getSsl();
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
ArrayList<Certificate> allCerts = new ArrayList<>();
for (String trustedCert : ssl.getTrustedX509Certificates()) {
try {
URL url = ResourceUtils.getURL(trustedCert);
Collection<? extends Certificate> certs = certificateFactory.generateCertificates(url.openStream());
allCerts.addAll(certs);
}
catch (IOException e) {
throw new RuntimeException("Could not load certificate '" + trustedCert + "'", e);
}
}
return allCerts.toArray(new X509Certificate[allCerts.size()]);
}
catch (CertificateException e1) {
throw new RuntimeException("Could not load CertificateFactory X.509", e1);
}
}
protected KeyManagerFactory getKeyManagerFactory() {
HttpClientProperties.Ssl ssl = properties.getSsl();
try {
if (ssl.getKeyStore() != null && ssl.getKeyStore().length() > 0) {
KeyManagerFactory keyManagerFactory = KeyManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
char[] keyPassword = ssl.getKeyPassword() != null ? ssl.getKeyPassword().toCharArray() : null;
if (keyPassword == null && ssl.getKeyStorePassword() != null) {
keyPassword = ssl.getKeyStorePassword().toCharArray();
}
keyManagerFactory.init(this.createKeyStore(), keyPassword);
return keyManagerFactory;
}
return null;
}
catch (Exception e) {
throw new IllegalStateException(e);
}
}
protected KeyStore createKeyStore() {
HttpClientProperties.Ssl ssl = properties.getSsl();
try {
KeyStore store = ssl.getKeyStoreProvider() != null
? KeyStore.getInstance(ssl.getKeyStoreType(), ssl.getKeyStoreProvider())
: KeyStore.getInstance(ssl.getKeyStoreType());
try {
URL url = ResourceUtils.getURL(ssl.getKeyStore());
store.load(url.openStream(),
ssl.getKeyStorePassword() != null ? ssl.getKeyStorePassword().toCharArray() : null);
}
catch (Exception e) {
throw new RuntimeException("Could not load key store ' " + ssl.getKeyStore() + "'", e);
}
return store;
}
catch (KeyStoreException | NoSuchProviderException e) {
throw new RuntimeException("Could not load KeyStore for given type and provider", e);
}
}
protected void setTrustManager(SslContextBuilder sslContextBuilder, X509Certificate... trustedX509Certificates) {
sslContextBuilder.trustManager(trustedX509Certificates);
}
protected void setTrustManager(SslContextBuilder sslContextBuilder, TrustManagerFactory factory) {
sslContextBuilder.trustManager(factory);
}
protected ConnectionProvider buildConnectionProvider(HttpClientProperties properties) {
HttpClientProperties.Pool pool = properties.getPool();

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2013-2020 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.cloud.gateway.config;
import java.security.cert.X509Certificate;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import reactor.netty.http.Http11SslContextSpec;
import reactor.netty.http.Http2SslContextSpec;
import reactor.netty.http.client.HttpClient;
import reactor.netty.tcp.SslProvider;
import org.springframework.boot.autoconfigure.web.ServerProperties;
public class HttpClientSslConfigurer extends AbstractSslConfigurer<HttpClient, HttpClient> {
private final ServerProperties serverProperties;
public HttpClientSslConfigurer(HttpClientProperties.Ssl sslProperties, ServerProperties serverProperties) {
super(sslProperties);
this.serverProperties = serverProperties;
}
public HttpClient configureSsl(HttpClient client) {
final HttpClientProperties.Ssl ssl = getSslProperties();
if ((ssl.getKeyStore() != null && ssl.getKeyStore().length() > 0)
|| getTrustedX509CertificatesForTrustManager().length > 0 || ssl.isUseInsecureTrustManager()) {
client = client.secure(sslContextSpec -> {
// configure ssl
configureSslContext(ssl, sslContextSpec);
});
}
return client;
}
protected void configureSslContext(HttpClientProperties.Ssl ssl, SslProvider.SslContextSpec sslContextSpec) {
SslProvider.ProtocolSslContextSpec clientSslContext = (serverProperties.getHttp2().isEnabled())
? Http2SslContextSpec.forClient() : Http11SslContextSpec.forClient();
clientSslContext.configure(sslContextBuilder -> {
X509Certificate[] trustedX509Certificates = getTrustedX509CertificatesForTrustManager();
if (trustedX509Certificates.length > 0) {
setTrustManager(sslContextBuilder, trustedX509Certificates);
}
else if (ssl.isUseInsecureTrustManager()) {
setTrustManager(sslContextBuilder, InsecureTrustManagerFactory.INSTANCE);
}
try {
sslContextBuilder.keyManager(getKeyManagerFactory());
}
catch (Exception e) {
logger.error(e);
}
});
sslContextSpec.sslContext(clientSslContext).handshakeTimeout(ssl.getHandshakeTimeout())
.closeNotifyFlushTimeout(ssl.getCloseNotifyFlushTimeout())
.closeNotifyReadTimeout(ssl.getCloseNotifyReadTimeout());
}
}

View File

@@ -45,8 +45,7 @@ import io.grpc.Channel;
import io.grpc.ClientCall;
import io.grpc.ManagedChannel;
import io.grpc.MethodDescriptor;
import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
import io.grpc.netty.NettyChannelBuilder;
import io.grpc.protobuf.ProtoUtils;
import io.grpc.stub.ClientCalls;
import io.netty.buffer.PooledByteBufAllocator;
@@ -54,7 +53,7 @@ import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.config.GRPCSSLContext;
import org.springframework.cloud.gateway.config.GrpcSslConfigurer;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
@@ -70,7 +69,6 @@ import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.web.server.ServerWebExchange;
import static io.grpc.netty.shaded.io.grpc.netty.NegotiationType.TLS;
import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
/**
@@ -85,13 +83,13 @@ import static org.springframework.cloud.gateway.support.GatewayToStringStyler.fi
public class JsonToGrpcGatewayFilterFactory
extends AbstractGatewayFilterFactory<JsonToGrpcGatewayFilterFactory.Config> {
private final GRPCSSLContext sslContext;
private final GrpcSslConfigurer grpcSslConfigurer;
private final ResourceLoader resourceLoader;
public JsonToGrpcGatewayFilterFactory(GRPCSSLContext sslContext, ResourceLoader resourceLoader) {
public JsonToGrpcGatewayFilterFactory(GrpcSslConfigurer grpcSslConfigurer, ResourceLoader resourceLoader) {
super(Config.class);
this.sslContext = sslContext;
this.grpcSslConfigurer = grpcSslConfigurer;
this.resourceLoader = resourceLoader;
}
@@ -298,11 +296,11 @@ public class JsonToGrpcGatewayFilterFactory
};
}
// We are creating this on every call, should optimize?
private ManagedChannel createChannelChannel(String host, int port) {
NettyChannelBuilder nettyChannelBuilder = NettyChannelBuilder.forAddress(host, port);
try {
return NettyChannelBuilder.forAddress(host, port).useTransportSecurity()
.sslContext(GrpcSslContexts.forClient().trustManager(sslContext.getTrustManager()).build())
.negotiationType(TLS).build();
return grpcSslConfigurer.configureSsl(nettyChannelBuilder);
}
catch (SSLException e) {
throw new RuntimeException(e);

View File

@@ -163,6 +163,37 @@ public class GatewayControllerEndpointTests {
.expectStatus().isCreated();
}
@Test
public void testPostValidShortcutRouteDefinition() {
RouteDefinition testRouteDefinition = new RouteDefinition();
testRouteDefinition.setId(
"gatewaywithgrpcfiltertest-0-104014-8916263311295787431172436062-test-gateway-tls-client-mapping-0");
testRouteDefinition.setUri(URI.create("https://localhost:8095"));
testRouteDefinition.setOrder(0);
testRouteDefinition.setMetadata(Collections.emptyMap());
FilterDefinition longFilterDefinition = new FilterDefinition();
FilterDefinition stripPrefix = new FilterDefinition();
stripPrefix.setName("StripPrefix");
stripPrefix.addArg("_genkey_0", "1");
longFilterDefinition.setName("JsonToGrpc");
longFilterDefinition.addArg("_genkey_0", "file:src/main/proto/hello.pb");
longFilterDefinition.addArg("_genkey_1", "file:src/main/proto/hello.proto");
longFilterDefinition.addArg("_genkey_2", "HelloService");
longFilterDefinition.addArg("_genkey_3", "hello");
testRouteDefinition.setFilters(Collections.singletonList(longFilterDefinition));
PredicateDefinition hostRoutePredicateDefinition = new PredicateDefinition();
hostRoutePredicateDefinition.setName("Path");
hostRoutePredicateDefinition.addArg("_genkey_0", "/json/hello");
testRouteDefinition.setPredicates(Arrays.asList(hostRoutePredicateDefinition));
testClient.post().uri("http://localhost:" + port + "/actuator/gateway/routes/test-route")
.accept(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(testRouteDefinition)).exchange()
.expectStatus().isCreated();
}
@Test
public void testPostRouteWithNotExistingFilter() {

View File

@@ -18,9 +18,12 @@ package org.springframework.cloud.gateway.config;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import io.netty.channel.ChannelOption;
@@ -51,6 +54,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint;
import org.springframework.cloud.gateway.actuate.GatewayLegacyControllerEndpoint;
import org.springframework.cloud.gateway.config.GatewayAutoConfigurationTests.CustomHttpClientFactory.CustomSslConfigurer;
import org.springframework.cloud.gateway.filter.factory.TokenRelayGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.headers.GRPCRequestHeadersFilter;
import org.springframework.cloud.gateway.filter.headers.GRPCResponseHeadersFilter;
@@ -96,7 +100,7 @@ public class GatewayAutoConfigurationTests {
assertThat(factory.connectionProvider.maxConnections()).isEqualTo(Integer.MAX_VALUE); // elastic
assertThat(factory.proxyProvider).isNull();
assertThat(factory.sslConfigured).isFalse();
assertThat(factory.isSslConfigured()).isFalse();
assertThat(httpClient.configuration().isAcceptGzip()).isFalse();
assertThat(httpClient.configuration().loggingHandler()).isNull();
@@ -147,8 +151,8 @@ public class GatewayAutoConfigurationTests {
assertThat(factory.proxyProvider).isNotNull();
assertThat(factory.proxyProvider.build().getAddress().get().getHostName()).isEqualTo("myhost");
assertThat(factory.sslConfigured).isTrue();
assertThat(factory.insecureTrustManagerSet).isTrue();
assertThat(factory.isSslConfigured()).isTrue();
assertThat(factory.isInsecureTrustManagerSet()).isTrue();
assertThat(context).hasSingleBean(ReactorNettyRequestUpgradeStrategy.class);
ReactorNettyRequestUpgradeStrategy upgradeStrategy = context
@@ -163,6 +167,28 @@ public class GatewayAutoConfigurationTests {
});
}
@Test
@Deprecated
public void nettyHttpClientNoSslConfigurerIsBackwardsCompatible() {
new ReactiveWebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class, MetricsAutoConfiguration.class,
SimpleMetricsExportAutoConfiguration.class, GatewayAutoConfiguration.class,
NoSslConfigurerCustomHttpClientFactoryConfig.class))
.withPropertyValues("spring.cloud.gateway.httpclient.ssl.use-insecure-trust-manager=true")
.run(context -> {
assertThat(context).hasSingleBean(HttpClient.class);
NoSslConfigurerHttpClientFactory factory = context.getBean(NoSslConfigurerHttpClientFactory.class);
assertThat(factory.configureSslCalled).isTrue();
assertThat(factory.configureSslContextCalled).isTrue();
assertThat(factory.getTrustedX509CertificatesForTrustManagerCalled).isTrue();
assertThat(factory.getKeyManagerFactoryCalled).isTrue();
assertThat(factory.createKeyStoreCalled).isFalse();
assertThat(factory.setTrustManagerCertCalled).isFalse();
assertThat(factory.setTrustManagerFactoryCalled).isTrue();
});
}
@Test
public void verboseActuatorEnabledByDefault() {
try (ConfigurableApplicationContext ctx = SpringApplication.run(Config.class, "--spring.jmx.enabled=false",
@@ -285,7 +311,7 @@ public class GatewayAutoConfigurationTests {
.withPropertyValues("server.http2.enabled=true").run(context -> {
assertThat(context).hasSingleBean(HttpClient.class);
CustomHttpClientFactory factory = context.getBean(CustomHttpClientFactory.class);
assertThat(factory.insecureTrustManagerSet).isFalse();
assertThat(factory.isInsecureTrustManagerSet()).isFalse();
});
}
@@ -310,25 +336,47 @@ public class GatewayAutoConfigurationTests {
@Bean
@Primary
CustomHttpClientFactory customHttpClientFactory(HttpClientProperties properties,
ServerProperties serverProperties, List<HttpClientCustomizer> customizers,
HttpClientSslConfigurer sslConfigurer) {
return new CustomHttpClientFactory(properties, serverProperties, sslConfigurer, customizers);
}
@Bean
@Primary
CustomSslConfigurer customSslContextFactory(ServerProperties serverProperties,
HttpClientProperties httpClientProperties) {
return new CustomSslConfigurer(httpClientProperties.getSsl(), serverProperties);
}
}
@Configuration
@EnableConfigurationProperties(ServerProperties.class)
@AutoConfigureBefore(GatewayAutoConfiguration.class)
@Deprecated
protected static class NoSslConfigurerCustomHttpClientFactoryConfig {
@Bean
@Primary
NoSslConfigurerHttpClientFactory noSslConfigurerHttpClientFactory(HttpClientProperties properties,
ServerProperties serverProperties, List<HttpClientCustomizer> customizers) {
return new CustomHttpClientFactory(properties, serverProperties, customizers);
return new NoSslConfigurerHttpClientFactory(properties, serverProperties, customizers);
}
}
protected static class CustomHttpClientFactory extends HttpClientFactory {
boolean insecureTrustManagerSet;
boolean sslConfigured;
private ConnectionProvider connectionProvider;
private ProxyProvider.Builder proxyProvider;
private CustomSslConfigurer customSslContextFactory;
public CustomHttpClientFactory(HttpClientProperties properties, ServerProperties serverProperties,
List<HttpClientCustomizer> customizers) {
super(properties, serverProperties, customizers);
HttpClientSslConfigurer sslConfigurer, List<HttpClientCustomizer> customizers) {
super(properties, serverProperties, sslConfigurer, customizers);
this.customSslContextFactory = (CustomSslConfigurer) sslConfigurer;
}
@Override
@@ -344,15 +392,106 @@ public class GatewayAutoConfigurationTests {
return proxyProvider;
}
public boolean isSslConfigured() {
return customSslContextFactory.sslConfigured;
}
public boolean isInsecureTrustManagerSet() {
return customSslContextFactory.insecureTrustManagerSet;
}
protected static class CustomSslConfigurer extends HttpClientSslConfigurer {
boolean sslConfigured;
boolean insecureTrustManagerSet;
protected CustomSslConfigurer(HttpClientProperties.Ssl sslProperties, ServerProperties serverProperties) {
super(sslProperties, serverProperties);
}
@Override
protected void configureSslContext(HttpClientProperties.Ssl ssl,
SslProvider.SslContextSpec sslContextSpec) {
sslConfigured = true;
super.configureSslContext(getSslProperties(), sslContextSpec);
}
@Override
protected void setTrustManager(SslContextBuilder sslContextBuilder, TrustManagerFactory factory) {
insecureTrustManagerSet = factory == InsecureTrustManagerFactory.INSTANCE;
super.setTrustManager(sslContextBuilder, factory);
}
}
}
/*
* Class to test backwards compatibility if no `SslConfigurer` used.
*/
@Deprecated
protected static class NoSslConfigurerHttpClientFactory extends HttpClientFactory {
boolean configureSslCalled;
boolean configureSslContextCalled;
boolean getTrustedX509CertificatesForTrustManagerCalled;
boolean getKeyManagerFactoryCalled;
boolean createKeyStoreCalled;
boolean setTrustManagerCertCalled;
boolean setTrustManagerFactoryCalled;
public NoSslConfigurerHttpClientFactory(HttpClientProperties properties, ServerProperties serverProperties,
List<HttpClientCustomizer> customizers) {
super(properties, serverProperties, customizers);
}
@Override
protected HttpClient configureSsl(HttpClient httpClient) {
configureSslCalled = true;
return super.configureSsl(httpClient);
}
@Override
protected void configureSslContext(HttpClientProperties.Ssl ssl, SslProvider.SslContextSpec sslContextSpec) {
sslConfigured = true;
configureSslContextCalled = true;
super.configureSslContext(ssl, sslContextSpec);
}
@Override
protected X509Certificate[] getTrustedX509CertificatesForTrustManager() {
getTrustedX509CertificatesForTrustManagerCalled = true;
return super.getTrustedX509CertificatesForTrustManager();
}
@Override
protected KeyManagerFactory getKeyManagerFactory() {
getKeyManagerFactoryCalled = true;
return super.getKeyManagerFactory();
}
@Override
protected KeyStore createKeyStore() {
createKeyStoreCalled = true;
return super.createKeyStore();
}
@Override
protected void setTrustManager(SslContextBuilder sslContextBuilder,
X509Certificate... trustedX509Certificates) {
setTrustManagerCertCalled = true;
super.setTrustManager(sslContextBuilder, trustedX509Certificates);
}
@Override
protected void setTrustManager(SslContextBuilder sslContextBuilder, TrustManagerFactory factory) {
insecureTrustManagerSet = factory == InsecureTrustManagerFactory.INSTANCE;
setTrustManagerFactoryCalled = true;
super.setTrustManager(sslContextBuilder, factory);
}