Commit a38ba340 by Dave Syer

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
parent b10bf9c3
...@@ -108,6 +108,16 @@ information will have a secure health check URL. Because of the way ...@@ -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 Eureka works internally, it will still publish a non-secure URL for
status and home page unless you also override those explicitly. 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 === Eureka's Health Checks
By default, Eureka uses the client heartbeat to determine if a client is up. By default, Eureka uses the client heartbeat to determine if a client is up.
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package org.springframework.cloud.netflix.ribbon; package org.springframework.cloud.netflix.ribbon;
import java.net.URI;
import org.apache.http.client.params.ClientPNames; import org.apache.http.client.params.ClientPNames;
import org.apache.http.client.params.CookiePolicy; import org.apache.http.client.params.CookiePolicy;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
...@@ -24,7 +26,10 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties ...@@ -24,7 +26,10 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy; 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.DefaultClientConfigImpl;
import com.netflix.client.config.IClientConfig; import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ConfigurationBasedServerList; import com.netflix.loadbalancer.ConfigurationBasedServerList;
...@@ -39,6 +44,7 @@ import com.netflix.loadbalancer.ServerListFilter; ...@@ -39,6 +44,7 @@ import com.netflix.loadbalancer.ServerListFilter;
import com.netflix.loadbalancer.ZoneAvoidanceRule; import com.netflix.loadbalancer.ZoneAvoidanceRule;
import com.netflix.loadbalancer.ZoneAwareLoadBalancer; import com.netflix.loadbalancer.ZoneAwareLoadBalancer;
import com.netflix.niws.client.http.RestClient; import com.netflix.niws.client.http.RestClient;
import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
import com.netflix.servo.monitor.Monitors; import com.netflix.servo.monitor.Monitors;
import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.Client;
import com.sun.jersey.client.apache4.ApacheHttpClient4; import com.sun.jersey.client.apache4.ApacheHttpClient4;
...@@ -90,9 +96,9 @@ public class RibbonClientConfiguration { ...@@ -90,9 +96,9 @@ public class RibbonClientConfiguration {
/** /**
* Create a Netflix {@link RestClient} integrated with Ribbon if none already exists in the * 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. * created lazily if ever another component requires it.
* *
* @param config the configuration to use by the underlying Ribbon instance * @param config the configuration to use by the underlying Ribbon instance
* @param loadBalancer the load balancer 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 * @return a {@link RestClient} instances backed by Ribbon
...@@ -142,6 +148,27 @@ public class RibbonClientConfiguration { ...@@ -142,6 +148,27 @@ public class RibbonClientConfiguration {
} }
@Override @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() { protected Client apacheHttpClientSpecificInitialization() {
ApacheHttpClient4 apache = (ApacheHttpClient4) super ApacheHttpClient4 apache = (ApacheHttpClient4) super
.apacheHttpClientSpecificInitialization(); .apacheHttpClientSpecificInitialization();
......
...@@ -24,14 +24,17 @@ import org.springframework.cloud.client.ServiceInstance; ...@@ -24,14 +24,17 @@ import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest; import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import com.netflix.appinfo.InstanceInfo.PortType;
import com.netflix.client.config.CommonClientConfigKey; import com.netflix.client.config.CommonClientConfigKey;
import com.netflix.client.config.IClientConfig; import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server; import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerStats; import com.netflix.loadbalancer.ServerStats;
import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
import com.netflix.servo.monitor.Stopwatch; import com.netflix.servo.monitor.Stopwatch;
/** /**
...@@ -53,9 +56,9 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient { ...@@ -53,9 +56,9 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient {
RibbonLoadBalancerContext context = this.clientFactory RibbonLoadBalancerContext context = this.clientFactory
.getLoadBalancerContext(serviceId); .getLoadBalancerContext(serviceId);
Server server = new Server(instance.getHost(), instance.getPort()); Server server = new Server(instance.getHost(), instance.getPort());
boolean secure = isSecure(this.clientFactory, serviceId); boolean secure = isSecure(this.clientFactory, server, serviceId);
URI uri = original; URI uri = original;
if(secure) { if (secure) {
uri = UriComponentsBuilder.fromUri(uri).scheme("https").build().toUri(); uri = UriComponentsBuilder.fromUri(uri).scheme("https").build().toUri();
} }
return context.reconstructURIWithServer(server, uri); return context.reconstructURIWithServer(server, uri);
...@@ -67,7 +70,8 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient { ...@@ -67,7 +70,8 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient {
if (server == null) { if (server == null) {
return null; return null;
} }
return new RibbonServer(serviceId, server, isSecure(this.clientFactory, serviceId)); return new RibbonServer(serviceId, server,
isSecure(this.clientFactory, server, serviceId));
} }
@Override @Override
...@@ -76,7 +80,8 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient { ...@@ -76,7 +80,8 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient {
RibbonLoadBalancerContext context = this.clientFactory RibbonLoadBalancerContext context = this.clientFactory
.getLoadBalancerContext(serviceId); .getLoadBalancerContext(serviceId);
Server server = getServer(loadBalancer); 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); ServerStats serverStats = context.getServerStats(server);
context.noteOpenConnection(serverStats); context.noteOpenConnection(serverStats);
...@@ -93,20 +98,30 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient { ...@@ -93,20 +98,30 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient {
} }
return null; return null;
} }
private boolean isSecure(SpringClientFactory clientFactory, String serviceId) { private boolean isSecure(SpringClientFactory clientFactory, Server server,
String serviceId) {
IClientConfig config = clientFactory.getClientConfig(serviceId); IClientConfig config = clientFactory.getClientConfig(serviceId);
if(config != null) { if (config != null) {
return config.get(CommonClientConfigKey.IsSecure, false); 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, private void recordStats(RibbonLoadBalancerContext context, Stopwatch tracer,
ServerStats serverStats, Object entity, Throwable exception) { ServerStats serverStats, Object entity, Throwable exception) {
tracer.stop(); tracer.stop();
long duration = tracer.getDuration(TimeUnit.MILLISECONDS); 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) { protected Server getServer(String serviceId) {
...@@ -117,7 +132,7 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient { ...@@ -117,7 +132,7 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient {
if (loadBalancer == null) { if (loadBalancer == null) {
return 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) { protected ILoadBalancer getLoadBalancer(String serviceId) {
...@@ -128,7 +143,7 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient { ...@@ -128,7 +143,7 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient {
private String serviceId; private String serviceId;
private Server server; private Server server;
private boolean secure; private boolean secure;
protected RibbonServer(String serviceId, Server server) { protected RibbonServer(String serviceId, Server server) {
this(serviceId, server, false); this(serviceId, server, false);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment