FELIX-4330 - Make SSL headers configurable:
- applied a slightly different approach as suggested by [~fmeschbe] by splitting the header
name and value into 2 different configuration properties allowing any header variant to
be supported;
- also made the header used to obtain forwarded client certificates configurable, as it
appears to have at least one other representation in the wild;
- added metatype description for the managed service to document the configuration properties.
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1596230 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/http/sslfilter/pom.xml b/http/sslfilter/pom.xml
index 682c069..0f30f60 100644
--- a/http/sslfilter/pom.xml
+++ b/http/sslfilter/pom.xml
@@ -44,6 +44,13 @@
<build>
<plugins>
<plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.5</source>
+ <target>1.5</target>
+ </configuration>
+ </plugin>
+ <plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
@@ -52,6 +59,13 @@
<Bundle-Activator>
org.apache.felix.http.sslfilter.internal.SslFilterActivator
</Bundle-Activator>
+ <Import-Package>
+ org.osgi.service.cm;resolution:=optional,
+ *
+ </Import-Package>
+ <DynamicImport-Package>
+ org.osgi.service.cm;version="[1.2,2)"
+ </DynamicImport-Package>)
</instructions>
</configuration>
</plugin>
@@ -77,7 +91,7 @@
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.http.api</artifactId>
- <version>2.3.0-SNAPSHOT</version>
+ <version>${felix.http.api.version}</version>
</dependency>
<!-- Test Dependencies -->
diff --git a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/HttpServiceTracker.java b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/HttpServiceTracker.java
index 82f85f9..b9b7576 100644
--- a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/HttpServiceTracker.java
+++ b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/HttpServiceTracker.java
@@ -18,15 +18,24 @@
*/
package org.apache.felix.http.sslfilter.internal;
-import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.Dictionary;
import java.util.Hashtable;
-import java.util.Map;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
import javax.servlet.ServletException;
import org.apache.felix.http.api.ExtHttpService;
+import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
import org.osgi.service.log.LogService;
import org.osgi.util.tracker.ServiceTracker;
@@ -35,13 +44,56 @@
*/
public class HttpServiceTracker extends ServiceTracker
{
- private final Map<ServiceReference, SslFilter> filters;
+ private final ConcurrentMap<ServiceReference, SslFilter> filters;
+ private ServiceRegistration configReceiver;
+
+ @SuppressWarnings("serial")
public HttpServiceTracker(BundleContext context)
{
super(context, ExtHttpService.class.getName(), null);
- this.filters = new HashMap<ServiceReference, SslFilter>();
+ this.filters = new ConcurrentHashMap<ServiceReference, SslFilter>();
+ }
+
+ @Override
+ public void open(boolean trackAllServices)
+ {
+ Dictionary<String, Object> props = new Hashtable<String, Object>();
+ props.put(Constants.SERVICE_PID, SslFilter.PID);
+
+ this.configReceiver = super.context.registerService(ManagedService.class.getName(), new ServiceFactory()
+ {
+ public Object getService(Bundle bundle, ServiceRegistration registration)
+ {
+ return new ManagedService()
+ {
+ public void updated(@SuppressWarnings("rawtypes") Dictionary properties) throws ConfigurationException
+ {
+ configureFilters(properties);
+ }
+ };
+ }
+
+ public void ungetService(Bundle bundle, ServiceRegistration registration, Object service)
+ {
+ // Nop
+ }
+ }, props);
+
+ super.open(trackAllServices);
+ }
+
+ @Override
+ public void close()
+ {
+ super.close();
+
+ if (this.configReceiver != null)
+ {
+ this.configReceiver.unregister();
+ this.configReceiver = null;
+ }
}
public Object addingService(ServiceReference reference)
@@ -54,7 +106,7 @@
{
service.registerFilter(filter, ".*", new Hashtable(), 0, null);
- this.filters.put(reference, filter);
+ this.filters.putIfAbsent(reference, filter);
SystemLogger.log(LogService.LOG_DEBUG, "SSL filter registered...");
}
@@ -69,7 +121,7 @@
public void removedService(ServiceReference reference, Object service)
{
- SslFilter filter = (SslFilter) this.filters.remove(reference);
+ SslFilter filter = this.filters.remove(reference);
if (filter != null)
{
((ExtHttpService) service).unregisterFilter(filter);
@@ -79,4 +131,13 @@
super.removedService(reference, service);
}
+
+ void configureFilters(@SuppressWarnings("rawtypes") final Dictionary properties) throws ConfigurationException
+ {
+ List<SslFilter> filters = new ArrayList<SslFilter>(this.filters.values());
+ for (SslFilter sslFilter : filters)
+ {
+ sslFilter.configure(properties);
+ }
+ }
}
diff --git a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilter.java b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilter.java
index 7460ea0..21ec51b 100644
--- a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilter.java
+++ b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilter.java
@@ -20,6 +20,7 @@
import java.io.IOException;
import java.security.cert.CertificateException;
+import java.util.Dictionary;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
@@ -29,33 +30,45 @@
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
+import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.log.LogService;
public class SslFilter implements Filter
{
- // request header indicating an SSL endpoint proxy
- private static final String X_FORWARD_SSL_HEADER = "X-Forwarded-SSL";
+ public static final String PID = "org.apache.felix.http.sslfilter.SslFilter";
- // value indicating an SSL endpoint proxy
- private static final String X_FORWARD_SSL_VALUE = "on";
+ private static final String DEFAULT_SSL_HEADER = "X-Forwarded-SSL";
+ private static final String DEFAULT_SSL_VALUE = "on";
+ private static final String DEFAULT_CERT_HEADER = "X-Forwarded-SSL-Certificate";
- // request header indicating an SSL client certificate (if available)
- private static final String X_FORWARD_SSL_CERTIFICATE_HEADER = "X-Forwarded-SSL-Certificate";
+ private static final String PROP_SSL_HEADER = "ssl-forward.header";
+ private static final String PROP_SSL_VALUE = "ssl-forward.value";
+ private static final String PROP_SSL_CERT_KEY = "ssl-forward-cert.header";
- public void init(FilterConfig config)
+ private volatile ConfigHolder config;
+
+ SslFilter()
{
- // No explicit initialization needed...
+ this.config = new ConfigHolder(DEFAULT_SSL_HEADER, DEFAULT_SSL_VALUE, DEFAULT_CERT_HEADER);
+ }
+
+ public void destroy()
+ {
+ // No explicit destroy needed...
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException
{
+ final ConfigHolder cfg = this.config;
+
HttpServletRequest httpReq = (HttpServletRequest) req;
- if (X_FORWARD_SSL_VALUE.equalsIgnoreCase(httpReq.getHeader(X_FORWARD_SSL_HEADER)))
+ if (cfg.sslValue.equalsIgnoreCase(httpReq.getHeader(cfg.sslHeader)))
{
try
{
- // In case this fails, we fall back to the original HTTP request, which is better than nothing...
- httpReq = new SslFilterRequest(httpReq, httpReq.getHeader(X_FORWARD_SSL_CERTIFICATE_HEADER));
+ // In case this fails, we fall back to the original HTTP
+ // request, which is better than nothing...
+ httpReq = new SslFilterRequest(httpReq, httpReq.getHeader(cfg.certHeader));
}
catch (CertificateException e)
{
@@ -78,8 +91,65 @@
}
}
- public void destroy()
+ public void init(FilterConfig config)
{
- // No explicit destroy needed...
+ // make sure there is some configuration
+ }
+
+ void configure(@SuppressWarnings("rawtypes") final Dictionary properties) throws ConfigurationException
+ {
+ String certHeader = DEFAULT_CERT_HEADER;
+ String sslHeader = DEFAULT_SSL_HEADER;
+ String sslValue = DEFAULT_SSL_VALUE;
+
+ if (properties != null)
+ {
+ certHeader = getOptionalString(properties, PROP_SSL_CERT_KEY);
+ sslHeader = getMandatoryString(properties, PROP_SSL_HEADER);
+ sslValue = getMandatoryString(properties, PROP_SSL_VALUE);
+ }
+
+ this.config = new ConfigHolder(sslHeader, sslValue, certHeader);
+
+ SystemLogger.log(LogService.LOG_INFO, "SSL filter (re)configured with: " + "SSL forward header = '" + sslHeader + "'; SSL forward value = '" + sslValue + "'; SSL certificate header = '"
+ + certHeader + "'.");
+ }
+
+ private String getOptionalString(@SuppressWarnings("rawtypes") Dictionary properties, String key) throws ConfigurationException
+ {
+ Object raw = properties.get(key);
+ if (raw == null || "".equals(((String) raw).trim()))
+ {
+ return null;
+ }
+ if (!(raw instanceof String))
+ {
+ throw new ConfigurationException(key, "invalid value");
+ }
+ return ((String) raw).trim();
+ }
+
+ private String getMandatoryString(@SuppressWarnings("rawtypes") Dictionary properties, String key) throws ConfigurationException
+ {
+ String value = getOptionalString(properties, key);
+ if (value == null)
+ {
+ throw new ConfigurationException(key, "missing value");
+ }
+ return value;
+ }
+
+ static class ConfigHolder
+ {
+ final String certHeader;
+ final String sslHeader;
+ final String sslValue;
+
+ public ConfigHolder(String sslHeader, String sslValue, String certHeader)
+ {
+ this.sslHeader = sslHeader;
+ this.sslValue = sslValue;
+ this.certHeader = certHeader;
+ }
}
}
diff --git a/http/sslfilter/src/main/resources/OSGI-INF/metatype/org.apache.felix.http.sslfilter.SslFilter.properties b/http/sslfilter/src/main/resources/OSGI-INF/metatype/org.apache.felix.http.sslfilter.SslFilter.properties
new file mode 100644
index 0000000..8c22736
--- /dev/null
+++ b/http/sslfilter/src/main/resources/OSGI-INF/metatype/org.apache.felix.http.sslfilter.SslFilter.properties
@@ -0,0 +1,24 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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
+#
+# http://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.
+name=Apache Felix Http Service SSL Filter
+description=Configuration for the Http Service SSL Filter. Please consult the documentation of your proxy for the actual headers and values to use.
+ssl-forward.header.name=SSL forward header
+ssl-forward.header.description=HTTP Request header name that indicates a request is a SSL request terminated at a proxy between the client and the originating server. The default value is 'X-Forwarded-SSL' as is customarily used in the wild. Other commonly used names are\: 'X-Forwarded-Proto' (Amazon ELB), 'X-Forwarded-Protocol' (alternative), and 'Front-End-Https' (Microsoft IIS).
+ssl-forward.value.name=SSL forward value
+ssl-forward.value.description=HTTP Request header value that indicates a request is a SSL request terminated at a proxy. The default value is 'on'. Another commonly used value is 'https'.
+ssl-forward-cert.header.name=SSL client header
+ssl-forward-cert.header.description=HTTP Request header name that contains the client certificate forwarded by a proxy. The default value is 'X-Forwarded-SSL-Certificate'. Another commonly used value is 'X-Forwarded-SSL-Client-Cert'.
diff --git a/http/sslfilter/src/main/resources/OSGI-INF/metatype/org.apache.felix.http.sslfilter.SslFilter.xml b/http/sslfilter/src/main/resources/OSGI-INF/metatype/org.apache.felix.http.sslfilter.SslFilter.xml
new file mode 100644
index 0000000..c3bc0b5
--- /dev/null
+++ b/http/sslfilter/src/main/resources/OSGI-INF/metatype/org.apache.felix.http.sslfilter.SslFilter.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metatype:MetaData xmlns:metatype="http://www.osgi.org/xmlns/metatype/v1.0.0" localization="OSGI-INF/metatype/org.apache.felix.http.sslfilter.SslFilter">
+ <OCD id="org.apache.felix.http.sslfilter.SslFilter" name="%name" description="%description">
+ <AD id="ssl-forward.header" name="%ssl-forward.header.name" description="%ssl-forward.header.description"
+ type="String" default="X-Forwarded-SSL" required="true" />
+ <AD id="ssl-forward.value" name="%ssl-forward.value.name" description="%ssl-forward.value.description"
+ type="String" default="on" required="true" />
+ <AD id="ssl-forward-cert.header" name="%ssl-forward-cert.header.name" description="%ssl-forward-cert.header.description"
+ type="String" default="X-Forwarded-SSL-Certificate" required="false"/>
+ </OCD>
+ <Designate pid="org.apache.felix.http.sslfilter.SslFilter">
+ <Object ocdref="org.apache.felix.http.sslfilter.SslFilter"/>
+ </Designate>
+</metatype:MetaData>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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
+
+ http://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.
+-->