Adds support for 'use-insecure-trust-manager' & 'trustedX509Certificates' for GRPC Filter
* Generated certificate with domain name (required for 'trustedX509Certificates') * Injected SslProperties in GRPC components to configure SslContext (ideally we want to reuse, not replicate, SCG context)
This commit is contained in:
committed by
spencergibb
parent
22a9aec8bf
commit
774975a0fe
@@ -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]
|
||||
----
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -20,23 +20,20 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
|
||||
import io.grpc.Grpc;
|
||||
import io.grpc.Server;
|
||||
import io.grpc.ServerCredentials;
|
||||
import io.grpc.TlsServerCredentials;
|
||||
import io.grpc.netty.shaded.io.grpc.netty.NettySslContextServerCredentials;
|
||||
import io.grpc.netty.shaded.io.netty.handler.ssl.ApplicationProtocolConfig;
|
||||
import io.grpc.netty.shaded.io.netty.handler.ssl.ApplicationProtocolNames;
|
||||
import io.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder;
|
||||
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.core.env.Environment;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -47,61 +44,39 @@ import org.springframework.stereotype.Component;
|
||||
@EnableAutoConfiguration
|
||||
public class GRPCApplication {
|
||||
|
||||
private static final int GRPC_SERVER_PORT = 8095;
|
||||
|
||||
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,29 +91,10 @@ public class GRPCApplication {
|
||||
private ServerCredentials createServerCredentials() throws IOException {
|
||||
File certChain = new ClassPathResource("public.cert").getFile();
|
||||
File privateKey = new ClassPathResource("private.key").getFile();
|
||||
File keystore = new ClassPathResource("keystore.p12").getFile();
|
||||
// return nettyCredentials(certChain, privateKey);
|
||||
|
||||
return TlsServerCredentials.create(certChain, privateKey);
|
||||
}
|
||||
|
||||
private ServerCredentials nettyCredentials(File certChain, File privateKey)
|
||||
throws SSLException {
|
||||
ApplicationProtocolConfig apn = new ApplicationProtocolConfig(
|
||||
ApplicationProtocolConfig.Protocol.ALPN,
|
||||
// NO_ADVERTISE is currently the only mode supported by both OpenSsl
|
||||
// and JDK providers.
|
||||
ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,
|
||||
// ACCEPT is currently the only mode supported by both OpenSsl and JDK
|
||||
// providers.
|
||||
ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT,
|
||||
ApplicationProtocolNames.HTTP_2, ApplicationProtocolNames.HTTP_1_1);
|
||||
|
||||
ServerCredentials serverCredentials = NettySslContextServerCredentials
|
||||
.create(SslContextBuilder.forServer(certChain, privateKey)
|
||||
.applicationProtocolConfig(apn).build());
|
||||
return serverCredentials;
|
||||
}
|
||||
|
||||
private void stop() throws InterruptedException {
|
||||
if (server != null) {
|
||||
server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
|
||||
@@ -148,15 +104,12 @@ public class GRPCApplication {
|
||||
static class HelloService extends HelloServiceGrpc.HelloServiceImplBase {
|
||||
|
||||
@Override
|
||||
public void hello(HelloRequest request,
|
||||
StreamObserver<HelloResponse> responseObserver) {
|
||||
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();
|
||||
HelloResponse response = HelloResponse.newBuilder().setGreeting(greeting).build();
|
||||
|
||||
responseObserver.onNext(response);
|
||||
responseObserver.onCompleted();
|
||||
|
||||
0
spring-cloud-gateway-integration-tests/grpc/src/main/resources/application.yml
Executable file → Normal file
0
spring-cloud-gateway-integration-tests/grpc/src/main/resources/application.yml
Executable file → Normal file
Binary file not shown.
54
spring-cloud-gateway-integration-tests/grpc/src/main/resources/private.key
Executable file → Normal file
54
spring-cloud-gateway-integration-tests/grpc/src/main/resources/private.key
Executable file → Normal file
@@ -1,32 +1,32 @@
|
||||
Bag Attributes
|
||||
friendlyName: bootapp
|
||||
localKeyID: 54 69 6D 65 20 31 36 35 39 37 31 32 34 31 30 37 34 35
|
||||
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-----
|
||||
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCr+JyQOvGQcuSm
|
||||
t1BN615YtrI0IoRFxfWHcbvU26A3TiGQtlnvXUIUTZApm1uSLR0o2VeS6ph+i3Ku
|
||||
WFZTjF9wTem4IY8U22f4SymD7tFU/X4//YL0GgcyJCT/gGaYY58EVEH8+K3Pz1Hy
|
||||
z/UnNuRrttxo+u94tB3OgTAYBB1KqY7Bqe7iZGleNz5baatOzYfEXmW4owHmqnfE
|
||||
LZAmXdFj55Rbndkr0LBiKkBHM8aDC1l0OSJBzKt6RF07+we8Nte1Oynd2F3lZRHa
|
||||
D8Pk7leDuV+KjC4W0X6kkwvZftmT/Y9C18+t2+J1BAz0kMsDjH1P87BconqVEhcM
|
||||
PehXGgXFAgMBAAECggEAIzrxSBLrPf5rnUPcrbnUQDRdWZTgqDKf1DmWk0rTDcFx
|
||||
2uWgkwr16JbjO8LaBZ48ZQvxhuWMjBAhVFpAhSkyvB0aDmDBoTI5oII1ZRPdyp2L
|
||||
6awT0dIrOzhwY+94FSwDfa2NPzfq07HTRf0YagoyzWZOzSrrOD0eBhotMh5Vqd+w
|
||||
UJ3VJCAWC0/Nz/4N5Ia2Hkyn2fz4fXdzux+/s5UKqaEHCXQWYmHj0XFLYSUb9rrO
|
||||
qMYKM8sJjma4R3XthNalZEd6TD+07jNl6BwFmMBMj6D0VBWxV02woKkoWltmydUD
|
||||
XhkdikCAB33CvHoj56PqCAFHrI6tnSRs9UqF/HPGQQKBgQDlfTJ3l2Orq1hy/D3d
|
||||
8Z3ndOthwjm7QLOX8dJi4CHtymX8Lg4qfHcrs5ltoazXMYgdFHLX1rNs+gJg9I5p
|
||||
vQS2jpRJ3BMzddrHaXjqhHv8WY1VYkPGML2tPol51LU7fx+2g+YnMzWNB7AIUz9K
|
||||
OZkvlPWRO6jOcjvGgLjKARQUzQKBgQC/1mgGpKKBnAfEoDGZ1SWpI21BFKL3DWMx
|
||||
YaOeFx+DETfAoUeIu1Ge2u8AIhrNE4dT8xOcG0vwGzHhKV63pabbMZ4+2pE92SEw
|
||||
U6VsGvd/lcKPJvxgLS3xFQEzEmgL4v1mSBsxqaGZf4XHpdb32bQeybAQwUm25MCw
|
||||
sEq/ZkX02QKBgAJOvvoq4IqyX2JQnQKlUlQofdFu1YvHe8bUXKw32r98YIgnombU
|
||||
95HN7YYHsSg8zESWlw0KkKVQ4kM9Uk1H5Es9pcoUV3EWB0woCFo7WM/RyrUIFuxg
|
||||
QxgB3/oEpInjnlsEeoT8Y9Z2NFPxGlrRt3OeCNYcBneE+IrncnQ9jIklAoGACUF0
|
||||
/20OEibPESORgRSRnpmA+fTe8ACLWqSVWllPQemgwQCHDQfMpld11JHQrThV/Szr
|
||||
M0r13P3S6EQwt7ecV1MLiYjOHSfOvCAtCQw8CMHXA0UkRBep8cnLpwUqU/h9tWOh
|
||||
PEIs89T9RWaw+oBcemfMwOIyhkp/KYc39AghM/ECgYAdTIT7J613mg8Ck/eUOBwx
|
||||
udT1wbpcrCqHJX8TKWmBOLh9teZvmq7OIuXGudf1tDRjw85p/7bYBcG+gvaiH0Yf
|
||||
qz5Npr4/+knxI2CBF2m81ln4Otq3X5siDL1wPCswawpnfdH+0qyfH6nxeGy0XSj2
|
||||
IQMa/LX1XqbUCry4sFl0yQ==
|
||||
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-----
|
||||
|
||||
35
spring-cloud-gateway-integration-tests/grpc/src/main/resources/public.cert
Executable file → Normal file
35
spring-cloud-gateway-integration-tests/grpc/src/main/resources/public.cert
Executable file → Normal file
@@ -1,21 +1,18 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDezCCAmOgAwIBAgIIAZ+HRnyzONYwDQYJKoZIhvcNAQELBQAwbDEQMA4GA1UE
|
||||
BhMHVW5rbm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQ
|
||||
MA4GA1UEChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5r
|
||||
bm93bjAeFw0yMjA4MDUxNTEzMzBaFw0zMjA4MDIxNTEzMzBaMGwxEDAOBgNVBAYT
|
||||
B1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vua25vd24xEDAO
|
||||
BgNVBAoTB1Vua25vd24xEDAOBgNVBAsTB1Vua25vd24xEDAOBgNVBAMTB1Vua25v
|
||||
d24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCr+JyQOvGQcuSmt1BN
|
||||
615YtrI0IoRFxfWHcbvU26A3TiGQtlnvXUIUTZApm1uSLR0o2VeS6ph+i3KuWFZT
|
||||
jF9wTem4IY8U22f4SymD7tFU/X4//YL0GgcyJCT/gGaYY58EVEH8+K3Pz1Hyz/Un
|
||||
NuRrttxo+u94tB3OgTAYBB1KqY7Bqe7iZGleNz5baatOzYfEXmW4owHmqnfELZAm
|
||||
XdFj55Rbndkr0LBiKkBHM8aDC1l0OSJBzKt6RF07+we8Nte1Oynd2F3lZRHaD8Pk
|
||||
7leDuV+KjC4W0X6kkwvZftmT/Y9C18+t2+J1BAz0kMsDjH1P87BconqVEhcMPehX
|
||||
GgXFAgMBAAGjITAfMB0GA1UdDgQWBBTC2DG76TuGW8VAEJawy96C2VPBpDANBgkq
|
||||
hkiG9w0BAQsFAAOCAQEAduMMKQvGMMbmN6OdnrV9oBzpphKs1BWvbDBPsSrFm1zX
|
||||
d1yRLSn2GyICQIA7SNX6Z59rWHe2HSXtzW6gbXm0yvAcPBI1DcRbamO12VF75Xjl
|
||||
yg9oGuwckIcK2YtqYw7iTjFHyDFShTdWpYePxmyZ4HyZKWX3gKn1hGHA0SWYUJY+
|
||||
HkfPat0iwuwXDfAoyMchZrd7mOsXAhlD6+39gw9Vr+WhRFhv29hU8uuzcY+ahZfQ
|
||||
9H8OF2nW0ePcc41909uorWfa3hWAVA2xfvzoBunFVMJt6mzT5M2Es5I/0wSXflbU
|
||||
e4NjO4usVx7aU3jPkHEPGlDrwFJ8olDpKPutr2dGVA==
|
||||
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-----
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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 {
|
||||
@@ -58,12 +69,18 @@ public class JsonToGrpcApplicationTests {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
restTemplate = createUnsecureClient();
|
||||
// restTemplate = new RestTemplate();
|
||||
}
|
||||
|
||||
@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();
|
||||
@@ -95,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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,19 +2,21 @@ 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
|
||||
# key-store-provider: SUN
|
||||
# trust-certificate: classpath:certificate.pem
|
||||
# trust-certificate-private-key: classpath:private.key
|
||||
# trust-store: classpath:keystore.jks
|
||||
# trust-store-password: password
|
||||
# trust-certificate-private-key: classpath:private.key
|
||||
# trust-store-provider: classpath:keystore.jks
|
||||
|
||||
management:
|
||||
endpoint:
|
||||
health:
|
||||
show-details: when_authorized
|
||||
gateway:
|
||||
enabled: true
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: "*"
|
||||
|
||||
spring:
|
||||
cloud:
|
||||
@@ -24,27 +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
|
||||
# key-store-type: JKS
|
||||
# key-store: classpath:keystore.jks
|
||||
# key-store-password: password
|
||||
# trusted-x509-certificates: classpath:certificate.pem
|
||||
# trust-store: classpath:keystore.jks
|
||||
# trust-store-password: password
|
||||
# trust-certificate-private-key: classpath:private.key
|
||||
# key-store-provider: classpath:keystore.jks
|
||||
# key-password: password
|
||||
|
||||
routes:
|
||||
- uri: https://localhost:8095
|
||||
predicates:
|
||||
- Path=/json/hello
|
||||
filters:
|
||||
- JsonToGrpc=file:src/main/proto/hello.pb,file:src/main/proto/hello.proto,HelloService,hello
|
||||
# Requires faking domain name (modifying /etc/hosts)
|
||||
# trustedX509Certificates:
|
||||
# - classpath:public.cert
|
||||
|
||||
logging:
|
||||
level:
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,18 +16,9 @@
|
||||
|
||||
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.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
@@ -35,9 +26,6 @@ import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
||||
import reactor.netty.http.Http11SslContextSpec;
|
||||
import reactor.netty.http.Http2SslContextSpec;
|
||||
import reactor.netty.http.HttpProtocol;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.netty.http.client.HttpResponseDecoderSpec;
|
||||
@@ -50,7 +38,6 @@ import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.springframework.cloud.gateway.config.HttpClientProperties.Pool.PoolType.DISABLED;
|
||||
@@ -69,12 +56,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 = new HttpClientSslConfigurer(properties.getSsl(), serverProperties);
|
||||
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,6 +115,34 @@ public class HttpClientFactory extends AbstractFactoryBean<HttpClient> {
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
protected HttpClient configureSsl(HttpClient httpClient) {
|
||||
return sslConfigurer.configureSsl(httpClient);
|
||||
}
|
||||
|
||||
protected void configureSslContext(HttpClientProperties.Ssl ssl, SslProvider.SslContextSpec sslContextSpec) {
|
||||
sslConfigurer.configureSslContext(ssl, sslContextSpec);
|
||||
}
|
||||
|
||||
protected X509Certificate[] getTrustedX509CertificatesForTrustManager() {
|
||||
return sslConfigurer.getTrustedX509CertificatesForTrustManager();
|
||||
}
|
||||
|
||||
protected KeyManagerFactory getKeyManagerFactory() {
|
||||
return sslConfigurer.getKeyManagerFactory();
|
||||
}
|
||||
|
||||
protected KeyStore createKeyStore() {
|
||||
return sslConfigurer.createKeyStore();
|
||||
}
|
||||
|
||||
protected void setTrustManager(SslContextBuilder sslContextBuilder, X509Certificate... trustedX509Certificates) {
|
||||
sslConfigurer.setTrustManager(sslContextBuilder, trustedX509Certificates);
|
||||
}
|
||||
|
||||
protected void setTrustManager(SslContextBuilder sslContextBuilder, TrustManagerFactory factory) {
|
||||
sslConfigurer.setTrustManager(sslContextBuilder, factory);
|
||||
}
|
||||
|
||||
private HttpClient applyCustomizers(HttpClient httpClient) {
|
||||
if (!CollectionUtils.isEmpty(customizers)) {
|
||||
customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
|
||||
@@ -127,43 +153,6 @@ public class HttpClientFactory extends AbstractFactoryBean<HttpClient> {
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
protected HttpClient configureSsl(HttpClient httpClient) {
|
||||
HttpClientProperties.Ssl ssl = properties.getSsl();
|
||||
if ((ssl.getKeyStore() != null && ssl.getKeyStore().length() > 0)
|
||||
|| getTrustedX509CertificatesForTrustManager().length > 0 || ssl.isUseInsecureTrustManager()) {
|
||||
httpClient = httpClient.secure(sslContextSpec -> {
|
||||
// configure ssl
|
||||
configureSslContext(ssl, sslContextSpec);
|
||||
});
|
||||
}
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
protected HttpClient configureProxy(HttpClient httpClient) {
|
||||
// configure proxy if proxy host is set.
|
||||
if (StringUtils.hasText(properties.getProxy().getHost())) {
|
||||
@@ -201,83 +190,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();
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -165,21 +165,9 @@ public class GatewayControllerEndpointTests {
|
||||
|
||||
@Test
|
||||
public void testPostValidShortcutRouteDefinition() {
|
||||
// 04-08-2022 08:41:31.866 [reactor-http-epoll-1] TRACE org.springframework.web.HttpLogging.trace - [0a3d20a5-43] Decoded [RouteDefinition{id='gatewaywithgrpcfiltertest-0-104014-8916263311295787431172436062-test-gateway-tls-client-mapping-0', predicates=[PredicateDefinition{name='Path', args={_genkey_0=/json/hello}}], filters=[FilterDefinition{name='StripPrefix', args={_genkey_0=1}}, FilterDefinition{name='JsonToGrpcGatewayFilterFactory', args={_genkey_0=file:src/main/proto/hello.pb, _genkey_1=file:src/main/proto/hello.proto, _genkey_2=HelloService, _genkey_3=hello}}], uri=https://localhost:8095, order=0, metadata={}}]
|
||||
// 04-08-2022 08:41:31.870 [reactor-http-epoll-1] WARN o.s.c.g.a.GatewayControllerEndpoint.handleUnavailableDefinition - Invalid FilterDefinition: [JsonToGrpcGatewayFilterFactory]
|
||||
// 04-08-2022 08:41:31.882 [reactor-http-epoll-1] DEBUG o.s.w.s.h.ResponseStatusExceptionHandler.handle - [0a3d20a5-43] Resolved [ResponseStatusException: "400 BAD_REQUEST "Invalid FilterDefinition: [JsonToGrpcGatewayFilterFactory]""] for HTTP POST /actuator/gateway/routes/gatewaywithgrpcfiltertest-0-104014-8916263311295787431172436062-test-gateway-tls-client-mapping-0
|
||||
// 04-08-2022 08:41:31.883 [reactor-http-epoll-1] TRACE o.s.w.s.a.HttpWebHandlerAdapter.traceDebug - [0a3d20a5-43] Completed 400 BAD_REQUEST, headers={masked}
|
||||
//
|
||||
//[RouteDefinition{id='gatewaywithgrpcfiltertest-0-104014-8916263311295787431172436062-test-gateway-tls-client-mapping-0',
|
||||
// predicates=[PredicateDefinition{name='Path', args={_genkey_0=/json/hello}}],
|
||||
// filters=[FilterDefinition{name='StripPrefix', args={_genkey_0=1}},
|
||||
// FilterDefinition{name='JsonToGrpcGatewayFilterFactory',
|
||||
// args={_genkey_0=file:src/main/proto/hello.pb, _genkey_1=file:src/main/proto/hello.proto, _genkey_2=HelloService, _genkey_3=hello}}],
|
||||
|
||||
// uri=https://localhost:8095, order=0, metadata={}}]
|
||||
|
||||
RouteDefinition testRouteDefinition = new RouteDefinition();
|
||||
testRouteDefinition.setId("gatewaywithgrpcfiltertest-0-104014-8916263311295787431172436062-test-gateway-tls-client-mapping-0");
|
||||
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());
|
||||
@@ -187,20 +175,19 @@ public class GatewayControllerEndpointTests {
|
||||
FilterDefinition longFilterDefinition = new FilterDefinition();
|
||||
FilterDefinition stripPrefix = new FilterDefinition();
|
||||
stripPrefix.setName("StripPrefix");
|
||||
stripPrefix.addArg("_genkey_0","1");
|
||||
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");
|
||||
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));
|
||||
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()
|
||||
|
||||
@@ -51,6 +51,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 +97,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 +148,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
|
||||
@@ -285,7 +286,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 +311,32 @@ public class GatewayAutoConfigurationTests {
|
||||
@Bean
|
||||
@Primary
|
||||
CustomHttpClientFactory customHttpClientFactory(HttpClientProperties properties,
|
||||
ServerProperties serverProperties, List<HttpClientCustomizer> customizers) {
|
||||
return new CustomHttpClientFactory(properties, serverProperties, customizers);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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,16 +352,37 @@ public class GatewayAutoConfigurationTests {
|
||||
return proxyProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureSslContext(HttpClientProperties.Ssl ssl, SslProvider.SslContextSpec sslContextSpec) {
|
||||
sslConfigured = true;
|
||||
super.configureSslContext(ssl, sslContextSpec);
|
||||
public boolean isSslConfigured() {
|
||||
return customSslContextFactory.sslConfigured;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setTrustManager(SslContextBuilder sslContextBuilder, TrustManagerFactory factory) {
|
||||
insecureTrustManagerSet = factory == InsecureTrustManagerFactory.INSTANCE;
|
||||
super.setTrustManager(sslContextBuilder, factory);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,127 +1,127 @@
|
||||
///*
|
||||
// * 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.route.builder
|
||||
//
|
||||
//import org.junit.Test
|
||||
//import org.junit.runner.RunWith
|
||||
//import org.springframework.beans.factory.annotation.Autowired
|
||||
//import org.springframework.boot.autoconfigure.EnableAutoConfiguration
|
||||
//import org.springframework.boot.test.context.SpringBootTest
|
||||
//import org.springframework.cloud.gateway.support.ServerWebExchangeUtils
|
||||
//import org.springframework.context.annotation.Configuration
|
||||
//import org.springframework.mock.http.server.reactive.MockServerHttpRequest
|
||||
//import org.springframework.mock.web.server.MockServerWebExchange
|
||||
//import org.springframework.test.context.junit4.SpringRunner
|
||||
//import org.springframework.web.server.ServerWebExchange
|
||||
//import reactor.core.publisher.toMono
|
||||
//import reactor.test.StepVerifier
|
||||
//import java.net.URI
|
||||
//
|
||||
//@RunWith(SpringRunner::class)
|
||||
//@SpringBootTest(classes = arrayOf(Config::class))
|
||||
//class RouteDslTests {
|
||||
//
|
||||
// @Autowired
|
||||
// lateinit var builder: RouteLocatorBuilder
|
||||
//
|
||||
// @Test
|
||||
// fun sampleRouteDsl() {
|
||||
// val routeLocator = builder.routes {
|
||||
// route(id = "test") {
|
||||
// host("**.abc.org") and path("/image/png")
|
||||
// filters {
|
||||
// addResponseHeader("X-TestHeader", "foobar")
|
||||
// }
|
||||
// uri("http://httpbin.org:80")
|
||||
// }
|
||||
//
|
||||
// route(id = "test2") {
|
||||
// path("/image/webp") or path("/image/anotherone")
|
||||
// filters {
|
||||
// addResponseHeader("X-AnotherHeader", "baz")
|
||||
// addResponseHeader("X-AnotherHeader-2", "baz-2")
|
||||
// }
|
||||
// uri("https://httpbin.org:443")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// StepVerifier
|
||||
// .create(routeLocator.routes)
|
||||
// .expectNextMatches({
|
||||
// it.id == "test" && it.filters.size == 1 && it.uri == URI.create("http://httpbin.org:80")
|
||||
// })
|
||||
// .expectNextMatches({
|
||||
// it.id == "test2" && it.filters.size == 2 && it.uri == URI.create("https://httpbin.org:443")
|
||||
// })
|
||||
// .expectComplete()
|
||||
// .verify()
|
||||
//
|
||||
// val sampleExchange: ServerWebExchange = MockServerWebExchange.from(MockServerHttpRequest.get("/image/webp")
|
||||
// .header("Host", "test.abc.org").build())
|
||||
//
|
||||
// val filteredRoutes = routeLocator.routes.filter({
|
||||
// sampleExchange.attributes.put(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR, it.id)
|
||||
// it.predicate.apply(sampleExchange).toMono().block()
|
||||
// })
|
||||
//
|
||||
// StepVerifier.create(filteredRoutes)
|
||||
// .expectNextMatches({
|
||||
// it.id == "test2" && it.filters.size == 2 && it.uri == URI.create("https://httpbin.org:443")
|
||||
// })
|
||||
// .expectComplete()
|
||||
// .verify()
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun dslWithFunctionParameters() {
|
||||
// val routerLocator = builder.routes {
|
||||
// route(id = "test1", order = 10, uri = "http://httpbin.org") {
|
||||
// host("**.abc.org")
|
||||
// }
|
||||
// route(id = "test2", order = 10, uri = "http://someurl") {
|
||||
// host("**.abc.org")
|
||||
// uri("http://override-url")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// StepVerifier.create(routerLocator.routes)
|
||||
// .expectNextMatches({
|
||||
// it.id == "test1" &&
|
||||
// it.uri == URI.create("http://httpbin.org:80") &&
|
||||
// it.order == 10 &&
|
||||
// it.predicate.apply(MockServerWebExchange
|
||||
// .from(MockServerHttpRequest
|
||||
// .get("/someuri").header("Host", "test.abc.org")))
|
||||
// .toMono().block()
|
||||
// })
|
||||
// .expectNextMatches({
|
||||
// it.id == "test2" &&
|
||||
// it.uri == URI.create("http://override-url:80") &&
|
||||
// it.order == 10 &&
|
||||
// it.predicate.apply(MockServerWebExchange
|
||||
// .from(MockServerHttpRequest
|
||||
// .get("/someuri").header("Host", "test.abc.org")))
|
||||
// .toMono().block()
|
||||
// })
|
||||
// .expectComplete()
|
||||
// .verify()
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//@Configuration(proxyBeanMethods = false)
|
||||
//@EnableAutoConfiguration
|
||||
//open class Config {}
|
||||
/*
|
||||
* 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.route.builder
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest
|
||||
import org.springframework.mock.web.server.MockServerWebExchange
|
||||
import org.springframework.test.context.junit4.SpringRunner
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
import reactor.core.publisher.toMono
|
||||
import reactor.test.StepVerifier
|
||||
import java.net.URI
|
||||
|
||||
@RunWith(SpringRunner::class)
|
||||
@SpringBootTest(classes = arrayOf(Config::class))
|
||||
class RouteDslTests {
|
||||
|
||||
@Autowired
|
||||
lateinit var builder: RouteLocatorBuilder
|
||||
|
||||
@Test
|
||||
fun sampleRouteDsl() {
|
||||
val routeLocator = builder.routes {
|
||||
route(id = "test") {
|
||||
host("**.abc.org") and path("/image/png")
|
||||
filters {
|
||||
addResponseHeader("X-TestHeader", "foobar")
|
||||
}
|
||||
uri("http://httpbin.org:80")
|
||||
}
|
||||
|
||||
route(id = "test2") {
|
||||
path("/image/webp") or path("/image/anotherone")
|
||||
filters {
|
||||
addResponseHeader("X-AnotherHeader", "baz")
|
||||
addResponseHeader("X-AnotherHeader-2", "baz-2")
|
||||
}
|
||||
uri("https://httpbin.org:443")
|
||||
}
|
||||
}
|
||||
|
||||
StepVerifier
|
||||
.create(routeLocator.routes)
|
||||
.expectNextMatches({
|
||||
it.id == "test" && it.filters.size == 1 && it.uri == URI.create("http://httpbin.org:80")
|
||||
})
|
||||
.expectNextMatches({
|
||||
it.id == "test2" && it.filters.size == 2 && it.uri == URI.create("https://httpbin.org:443")
|
||||
})
|
||||
.expectComplete()
|
||||
.verify()
|
||||
|
||||
val sampleExchange: ServerWebExchange = MockServerWebExchange.from(MockServerHttpRequest.get("/image/webp")
|
||||
.header("Host", "test.abc.org").build())
|
||||
|
||||
val filteredRoutes = routeLocator.routes.filter({
|
||||
sampleExchange.attributes.put(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR, it.id)
|
||||
it.predicate.apply(sampleExchange).toMono().block()
|
||||
})
|
||||
|
||||
StepVerifier.create(filteredRoutes)
|
||||
.expectNextMatches({
|
||||
it.id == "test2" && it.filters.size == 2 && it.uri == URI.create("https://httpbin.org:443")
|
||||
})
|
||||
.expectComplete()
|
||||
.verify()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dslWithFunctionParameters() {
|
||||
val routerLocator = builder.routes {
|
||||
route(id = "test1", order = 10, uri = "http://httpbin.org") {
|
||||
host("**.abc.org")
|
||||
}
|
||||
route(id = "test2", order = 10, uri = "http://someurl") {
|
||||
host("**.abc.org")
|
||||
uri("http://override-url")
|
||||
}
|
||||
}
|
||||
|
||||
StepVerifier.create(routerLocator.routes)
|
||||
.expectNextMatches({
|
||||
it.id == "test1" &&
|
||||
it.uri == URI.create("http://httpbin.org:80") &&
|
||||
it.order == 10 &&
|
||||
it.predicate.apply(MockServerWebExchange
|
||||
.from(MockServerHttpRequest
|
||||
.get("/someuri").header("Host", "test.abc.org")))
|
||||
.toMono().block()
|
||||
})
|
||||
.expectNextMatches({
|
||||
it.id == "test2" &&
|
||||
it.uri == URI.create("http://override-url:80") &&
|
||||
it.order == 10 &&
|
||||
it.predicate.apply(MockServerWebExchange
|
||||
.from(MockServerHttpRequest
|
||||
.get("/someuri").header("Host", "test.abc.org")))
|
||||
.toMono().block()
|
||||
})
|
||||
.expectComplete()
|
||||
.verify()
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableAutoConfiguration
|
||||
open class Config {}
|
||||
Reference in New Issue
Block a user