From a38ba340490f37b6027aefe03e0aaf35455f16a7 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Mon, 28 Sep 2015 17:53:37 +0100 Subject: [PATCH] Try to guess whether a ribbon client is looking for HTTPS There are usually some hints available, especially if the Server came from Eureka. Unfortunately Server on it's own doesn't contain enough information (why?), but as a fallback we can guess that anyone using port 443 is secure. Fixes gh-459 --- .../main/asciidoc/spring-cloud-netflix.adoc | 10 +++++ .../ribbon/RibbonClientConfiguration.java | 31 +++++++++++++++- .../ribbon/RibbonLoadBalancerClient.java | 37 +++++++++++++------ 3 files changed, 65 insertions(+), 13 deletions(-) diff --git a/docs/src/main/asciidoc/spring-cloud-netflix.adoc b/docs/src/main/asciidoc/spring-cloud-netflix.adoc index 015967f7..3c9deefa 100644 --- a/docs/src/main/asciidoc/spring-cloud-netflix.adoc +++ b/docs/src/main/asciidoc/spring-cloud-netflix.adoc @@ -108,6 +108,16 @@ information will have a secure health check URL. Because of the way Eureka works internally, it will still publish a non-secure URL for status and home page unless you also override those explicitly. +NOTE: If your app is running behind a proxy, and the SSL termination +is in the proxy (e.g. if you run in Cloud Foundry or other platforms +as a service) then you will need to ensure that the proxy "forwarded" +headers are intercepted and handled by the application. An embedded +Tomcat container in a Spring Boot app does this automatically for most +proxies (since 1.3.0), but other containers might need explicit +configuration for the 'X-Forwarded-\*` headers. A sign that you got +this wrong will be that the links rendered by your app to itslef will be +wrong (the wrong host, port or protocol). + === Eureka's Health Checks By default, Eureka uses the client heartbeat to determine if a client is up. diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfiguration.java index 9a0e173b..63ce04ab 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfiguration.java @@ -16,6 +16,8 @@ package org.springframework.cloud.netflix.ribbon; +import java.net.URI; + import org.apache.http.client.params.ClientPNames; import org.apache.http.client.params.CookiePolicy; import org.springframework.beans.factory.annotation.Value; @@ -24,7 +26,10 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; +import org.springframework.util.ClassUtils; +import org.springframework.web.util.UriComponentsBuilder; +import com.netflix.appinfo.InstanceInfo.PortType; import com.netflix.client.config.DefaultClientConfigImpl; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.ConfigurationBasedServerList; @@ -39,6 +44,7 @@ import com.netflix.loadbalancer.ServerListFilter; import com.netflix.loadbalancer.ZoneAvoidanceRule; import com.netflix.loadbalancer.ZoneAwareLoadBalancer; import com.netflix.niws.client.http.RestClient; +import com.netflix.niws.loadbalancer.DiscoveryEnabledServer; import com.netflix.servo.monitor.Monitors; import com.sun.jersey.api.client.Client; import com.sun.jersey.client.apache4.ApacheHttpClient4; @@ -90,9 +96,9 @@ public class RibbonClientConfiguration { /** * Create a Netflix {@link RestClient} integrated with Ribbon if none already exists in the - * application context. It is not required for Ribbon to work properly and is therefore + * application context. It is not required for Ribbon to work properly and is therefore * created lazily if ever another component requires it. - * + * * @param config the configuration to use by the underlying Ribbon instance * @param loadBalancer the load balancer to use by the underlying Ribbon instance * @return a {@link RestClient} instances backed by Ribbon @@ -141,6 +147,27 @@ public class RibbonClientConfiguration { initWithNiwsConfig(ncc); } + @Override + public URI reconstructURIWithServer(Server server, URI original) { + String scheme = original.getScheme(); + if (!"https".equals(scheme) && isSecure(server)) { + original = UriComponentsBuilder.fromUri(original).scheme("https").build().toUri(); + } + return super.reconstructURIWithServer(server, original); + } + + private boolean isSecure(Server server) { + if (ClassUtils.isPresent("com.netflix.niws.loadbalancer.DiscoveryEnabledServer", + null)) { + if (server instanceof DiscoveryEnabledServer) { + DiscoveryEnabledServer enabled = (DiscoveryEnabledServer) server; + return enabled.getInstanceInfo().isPortEnabled(PortType.SECURE); + } + } + // Can we do better? + return (""+server.getPort()).endsWith("443"); + } + @Override protected Client apacheHttpClientSpecificInitialization() { ApacheHttpClient4 apache = (ApacheHttpClient4) super diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonLoadBalancerClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonLoadBalancerClient.java index 1bedb94e..edc92025 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonLoadBalancerClient.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonLoadBalancerClient.java @@ -24,14 +24,17 @@ import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import org.springframework.web.util.UriComponentsBuilder; +import com.netflix.appinfo.InstanceInfo.PortType; import com.netflix.client.config.CommonClientConfigKey; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; import com.netflix.loadbalancer.ServerStats; +import com.netflix.niws.loadbalancer.DiscoveryEnabledServer; import com.netflix.servo.monitor.Stopwatch; /** @@ -53,9 +56,9 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient { RibbonLoadBalancerContext context = this.clientFactory .getLoadBalancerContext(serviceId); Server server = new Server(instance.getHost(), instance.getPort()); - boolean secure = isSecure(this.clientFactory, serviceId); + boolean secure = isSecure(this.clientFactory, server, serviceId); URI uri = original; - if(secure) { + if (secure) { uri = UriComponentsBuilder.fromUri(uri).scheme("https").build().toUri(); } return context.reconstructURIWithServer(server, uri); @@ -67,7 +70,8 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient { if (server == null) { return null; } - return new RibbonServer(serviceId, server, isSecure(this.clientFactory, serviceId)); + return new RibbonServer(serviceId, server, + isSecure(this.clientFactory, server, serviceId)); } @Override @@ -76,7 +80,8 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient { RibbonLoadBalancerContext context = this.clientFactory .getLoadBalancerContext(serviceId); Server server = getServer(loadBalancer); - RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(clientFactory, serviceId)); + RibbonServer ribbonServer = new RibbonServer(serviceId, server, + isSecure(this.clientFactory, server, serviceId)); ServerStats serverStats = context.getServerStats(server); context.noteOpenConnection(serverStats); @@ -93,20 +98,30 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient { } return null; } - - private boolean isSecure(SpringClientFactory clientFactory, String serviceId) { + + private boolean isSecure(SpringClientFactory clientFactory, Server server, + String serviceId) { IClientConfig config = clientFactory.getClientConfig(serviceId); - if(config != null) { + if (config != null) { return config.get(CommonClientConfigKey.IsSecure, false); } - return false; + if (ClassUtils.isPresent("com.netflix.niws.loadbalancer.DiscoveryEnabledServer", + null)) { + if (server instanceof DiscoveryEnabledServer) { + DiscoveryEnabledServer enabled = (DiscoveryEnabledServer) server; + return enabled.getInstanceInfo().isPortEnabled(PortType.SECURE); + } + } + // Can we do better? + return ("" + server.getPort()).endsWith("443"); } private void recordStats(RibbonLoadBalancerContext context, Stopwatch tracer, ServerStats serverStats, Object entity, Throwable exception) { tracer.stop(); long duration = tracer.getDuration(TimeUnit.MILLISECONDS); - context.noteRequestCompletion(serverStats, entity, exception, duration, null/* errorHandler */); + context.noteRequestCompletion(serverStats, entity, exception, duration, + null/* errorHandler */); } protected Server getServer(String serviceId) { @@ -117,7 +132,7 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient { if (loadBalancer == null) { return null; } - return loadBalancer.chooseServer("default"); //TODO: better handling of key + return loadBalancer.chooseServer("default"); // TODO: better handling of key } protected ILoadBalancer getLoadBalancer(String serviceId) { @@ -128,7 +143,7 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient { private String serviceId; private Server server; private boolean secure; - + protected RibbonServer(String serviceId, Server server) { this(serviceId, server, false); }