You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by md...@apache.org on 2020/05/01 17:47:21 UTC
[lucene-solr] branch master updated: SOLR-14440 Cert Auth plugin
(#1463)
This is an automated email from the ASF dual-hosted git repository.
mdrob pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git
The following commit(s) were added to refs/heads/master by this push:
new 7b289d61 SOLR-14440 Cert Auth plugin (#1463)
7b289d61 is described below
commit 7b289d6185f30b316d07d5ae5755cfc70c97921d
Author: Mike Drob <md...@apache.org>
AuthorDate: Fri May 1 12:47:12 2020 -0500
SOLR-14440 Cert Auth plugin (#1463)
---
solr/CHANGES.txt | 2 +-
.../org/apache/solr/metrics/SolrMetricManager.java | 47 ++++++++-----
.../org/apache/solr/security/CertAuthPlugin.java | 51 ++++++++++++++
.../apache/solr/servlet/SolrDispatchFilter.java | 2 +-
.../apache/solr/security/CertAuthPluginTest.java | 79 ++++++++++++++++++++++
.../authentication-and-authorization-plugins.adoc | 2 +-
.../src/cert-authentication-plugin.adoc | 61 +++++++++++++++++
solr/solr-ref-guide/src/securing-solr.adoc | 1 +
solr/webapp/web/js/angular/controllers/login.js | 2 +-
solr/webapp/web/partials/login.html | 17 +++++
10 files changed, 244 insertions(+), 20 deletions(-)
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 3282f7c..50f2614 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -10,7 +10,7 @@ Consult the LUCENE_CHANGES.txt file for additional, low level, changes in this r
New Features
---------------------
-(No changes)
+* SOLR-14440: Introduce new Certificate Authentication Plugin to load Principal from certificate subject. (Mike Drob)
Improvements
----------------------
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
index 14843ba..517f8dd 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
@@ -527,11 +527,12 @@ public class SolrMetricManager {
*/
public enum ResolutionStrategy {
/**
- * The existing metric will be kept and the new metric will be ignored
+ * The existing metric will be kept and the new metric will be ignored. If no metric exists, then the new metric
+ * will be registered.
*/
IGNORE,
/**
- * The existing metric will be removed and replaced with the new metric
+ * The existing metric will be removed and replaced with the new metric.
*/
REPLACE,
/**
@@ -556,13 +557,11 @@ public class SolrMetricManager {
Map<String, Metric> existingMetrics = metricRegistry.getMetrics();
for (Map.Entry<String, Metric> entry : metrics.getMetrics().entrySet()) {
String fullName = mkName(entry.getKey(), metricPath);
- if (existingMetrics.containsKey(fullName)) {
- if (strategy == ResolutionStrategy.REPLACE) {
- metricRegistry.remove(fullName);
- } else if (strategy == ResolutionStrategy.IGNORE) {
- continue;
- } // strategy == ERROR will fail when we try to register later
- }
+ if (strategy == ResolutionStrategy.REPLACE) {
+ metricRegistry.remove(fullName);
+ } else if (strategy == ResolutionStrategy.IGNORE && existingMetrics.containsKey(fullName)) {
+ continue;
+ } // strategy == ERROR will fail when we try to register
metricRegistry.register(fullName, entry.getValue());
}
}
@@ -685,27 +684,35 @@ public class SolrMetricManager {
}
/**
+ * @deprecated use {@link #registerMetric(SolrMetricsContext, String, Metric, ResolutionStrategy, String, String...)}
+ */
+ @Deprecated
+ public void registerMetric(SolrMetricsContext context, String registry, Metric metric, boolean force, String metricName, String... metricPath) {
+ registerMetric(context, registry, metric, force ? ResolutionStrategy.REPLACE : ResolutionStrategy.IGNORE, metricName, metricPath);
+ }
+
+ /**
* Register an instance of {@link Metric}.
*
* @param registry registry name
* @param metric metric instance
- * @param force if true then an already existing metric with the same name will be replaced.
- * When false and a metric with the same name already exists an exception
- * will be thrown.
+ * @param strategy the conflict resolution strategy to use if the named metric already exists.
* @param metricName metric name, either final name or a fully-qualified name
* using dotted notation
* @param metricPath (optional) additional top-most metric name path elements
*/
- public void registerMetric(SolrMetricsContext context, String registry, Metric metric, boolean force, String metricName, String... metricPath) {
+ public void registerMetric(SolrMetricsContext context, String registry, Metric metric, ResolutionStrategy strategy, String metricName, String... metricPath) {
MetricRegistry metricRegistry = registry(registry);
String fullName = mkName(metricName, metricPath);
if (context != null) {
context.registerMetricName(fullName);
}
synchronized (metricRegistry) { // prevent race; register() throws if metric is already present
- if (force) { // must remove any existing one if present
+ if (strategy == ResolutionStrategy.REPLACE) { // must remove any existing one if present
metricRegistry.remove(fullName);
- }
+ } else if (strategy == ResolutionStrategy.IGNORE && metricRegistry.getMetrics().containsKey(fullName)) {
+ return;
+ } // strategy == ERROR will fail when we try to register
metricRegistry.register(fullName, metric);
}
}
@@ -740,8 +747,16 @@ public class SolrMetricManager {
}
}
+ /**
+ * @deprecated use {@link #registerGauge(SolrMetricsContext, String, Gauge, String, ResolutionStrategy, String, String...)}
+ */
+ @Deprecated
public void registerGauge(SolrMetricsContext context, String registry, Gauge<?> gauge, String tag, boolean force, String metricName, String... metricPath) {
- registerMetric(context, registry, new GaugeWrapper(gauge, tag), force, metricName, metricPath);
+ registerGauge(context, registry, gauge, tag, force ? ResolutionStrategy.REPLACE : ResolutionStrategy.ERROR, metricName, metricPath);
+ }
+
+ public void registerGauge(SolrMetricsContext context, String registry, Gauge<?> gauge, String tag, ResolutionStrategy strategy, String metricName, String... metricPath) {
+ registerMetric(context, registry, new GaugeWrapper(gauge, tag), strategy, metricName, metricPath);
}
public int unregisterGauges(String registryName, String tagSegment) {
diff --git a/solr/core/src/java/org/apache/solr/security/CertAuthPlugin.java b/solr/core/src/java/org/apache/solr/security/CertAuthPlugin.java
new file mode 100644
index 0000000..765aa89
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/security/CertAuthPlugin.java
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+package org.apache.solr.security;
+
+import org.apache.http.HttpHeaders;
+
+import javax.servlet.FilterChain;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.security.cert.X509Certificate;
+import java.util.Map;
+
+/**
+ * An authentication plugin that sets principal based on the certificate subject
+ */
+public class CertAuthPlugin extends AuthenticationPlugin {
+ @Override
+ public void init(Map<String, Object> pluginConfig) {
+
+ }
+
+ @Override
+ public boolean doAuthenticate(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws Exception {
+ X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
+ if (certs == null || certs.length == 0) {
+ numMissingCredentials.inc();
+ response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "Certificate");
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "require certificate");
+ return false;
+ }
+
+ HttpServletRequest wrapped = wrapWithPrincipal(request, certs[0].getSubjectX500Principal());
+ numAuthenticated.inc();
+ filterChain.doFilter(wrapped, response);
+ return true;
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
index eb2f74a..3cee4a1 100644
--- a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
+++ b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
@@ -226,7 +226,7 @@ public class SolrDispatchFilter extends BaseSolrFilter {
}
});
});
- metricManager.registerGauge(null, registryName, sysprops, metricTag, true, "properties", "system");
+ metricManager.registerGauge(null, registryName, sysprops, metricTag, SolrMetricManager.ResolutionStrategy.IGNORE, "properties", "system");
} catch (Exception e) {
log.warn("Error registering JVM metrics", e);
}
diff --git a/solr/core/src/test/org/apache/solr/security/CertAuthPluginTest.java b/solr/core/src/test/org/apache/solr/security/CertAuthPluginTest.java
new file mode 100644
index 0000000..fb32a21
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/security/CertAuthPluginTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+package org.apache.solr.security;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import javax.security.auth.x500.X500Principal;
+import javax.servlet.FilterChain;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import java.security.cert.X509Certificate;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class CertAuthPluginTest extends SolrTestCaseJ4 {
+ private CertAuthPlugin plugin;
+
+ @BeforeClass
+ public static void setupMockito() {
+ SolrTestCaseJ4.assumeWorkingMockito();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ plugin = new CertAuthPlugin();
+ }
+
+ @Test
+ public void testAuthenticateOk() throws Exception {
+ X500Principal principal = new X500Principal("CN=NAME");
+ X509Certificate certificate = mock(X509Certificate.class);
+ HttpServletRequest request = mock(HttpServletRequest.class);
+
+ when(certificate.getSubjectX500Principal()).thenReturn(principal);
+ when(request.getAttribute(any())).thenReturn(new X509Certificate[] { certificate });
+
+ FilterChain chain = (req, rsp) -> assertEquals(principal, ((HttpServletRequest) req).getUserPrincipal());
+ assertTrue(plugin.doAuthenticate(request, null, chain));
+
+ assertEquals(1, plugin.numAuthenticated.getCount());
+ }
+
+ @Test
+ public void testAuthenticateMissing() throws Exception {
+ HttpServletRequest request = mock(HttpServletRequest.class);
+ when(request.getAttribute(any())).thenReturn(null);
+
+ HttpServletResponse response = mock(HttpServletResponse.class);
+
+ assertFalse(plugin.doAuthenticate(request, response, null));
+ verify(response).sendError(eq(401), anyString());
+
+ assertEquals(1, plugin.numMissingCredentials.getCount());
+ }
+}
diff --git a/solr/solr-ref-guide/src/authentication-and-authorization-plugins.adoc b/solr/solr-ref-guide/src/authentication-and-authorization-plugins.adoc
index b8f9a3d..4e4a8c3 100644
--- a/solr/solr-ref-guide/src/authentication-and-authorization-plugins.adoc
+++ b/solr/solr-ref-guide/src/authentication-and-authorization-plugins.adoc
@@ -1,5 +1,5 @@
= Configuring Authentication, Authorization and Audit Logging
-:page-children: basic-authentication-plugin, hadoop-authentication-plugin, kerberos-authentication-plugin, jwt-authentication-plugin, rule-based-authorization-plugin, audit-logging
+:page-children: basic-authentication-plugin, hadoop-authentication-plugin, kerberos-authentication-plugin, jwt-authentication-plugin, cert-authentication-plugin, rule-based-authorization-plugin, audit-logging
// 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
diff --git a/solr/solr-ref-guide/src/cert-authentication-plugin.adoc b/solr/solr-ref-guide/src/cert-authentication-plugin.adoc
new file mode 100644
index 0000000..4b23cc9
--- /dev/null
+++ b/solr/solr-ref-guide/src/cert-authentication-plugin.adoc
@@ -0,0 +1,61 @@
+= Certificate Authentication Plugin
+// 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.
+
+Solr can support extracting the user principal out of the client's certificate with the use of the CertAuthPlugin.
+
+== Enable Certificate Authentication
+
+For Certificate authentication, the `security.json` file must have an `authentication` part which defines the class being used for authentication.
+
+An example `security.json` is shown below:
+
+[source,json]
+----
+{
+ "authentication": {
+ "class":"solr.CertAuthPlugin"
+ }
+}
+----
+
+=== Certificate Validation
+
+Parts of certificate validation, including verifying the trust chain and peer hostname/ip address will be done by the web servlet container before the request ever reaches the authentication plugin.
+These checks are described in the <<enabling-ssl.adoc#enabling-ssl,Enabling SSL>> section.
+
+This plugin provides no additional checking beyond what has been configured via SSL properties.
+
+=== User Principal Extraction
+
+This plugin will configure the user principal for the request based on the X500 subject present in the client certificate.
+Authorization plugins will need to accept and handle the full subject name, for example:
+
+[source]
+----
+CN=Solr User,OU=Engineering,O=Example Inc.,C=US
+----
+
+A list of possible tags that can be present in the subject name is available in https://tools.ietf.org/html/rfc5280#section-4.1.2.4[RFC-5280, Section 4.1.2.4]. Values may have spaces, punctuation, and other characters.
+
+It is best practice to verify the actual contents of certificates issued by your trusted certificate authority before configuring authorization based on the contents.
+
+== Using Certificate Auth with Clients (including SolrJ)
+
+With certificate authentication enabled, all client requests must include a valid certificate.
+This is identical to the <<enabling-ssl.adoc#example-client-actions,client requirements>> when using SSL.
+
diff --git a/solr/solr-ref-guide/src/securing-solr.adoc b/solr/solr-ref-guide/src/securing-solr.adoc
index c3e17e9..1d3baee 100644
--- a/solr/solr-ref-guide/src/securing-solr.adoc
+++ b/solr/solr-ref-guide/src/securing-solr.adoc
@@ -44,6 +44,7 @@ Authentication makes sure you know the identity of your users. The authenticatio
* <<basic-authentication-plugin.adoc#basic-authentication-plugin,Basic Authentication Plugin>>
* <<hadoop-authentication-plugin.adoc#hadoop-authentication-plugin,Hadoop Authentication Plugin>>
* <<jwt-authentication-plugin.adoc#jwt-authentication-plugin,JWT Authentication Plugin>>
+* <<cert-authentication-plugin.adoc#cert-authentication-plugin,Certificate Authentication Plugin>>
// end::list-of-authentication-plugins[]
=== Authorization Plugins
diff --git a/solr/webapp/web/js/angular/controllers/login.js b/solr/webapp/web/js/angular/controllers/login.js
index 8127c6f..b76ec1f 100644
--- a/solr/webapp/web/js/angular/controllers/login.js
+++ b/solr/webapp/web/js/angular/controllers/login.js
@@ -47,7 +47,7 @@ solrAdminApp.controller('LoginController',
sessionStorage.setItem("auth.scheme", authScheme);
}
- var supportedSchemes = ['Basic', 'Bearer', 'Negotiate'];
+ var supportedSchemes = ['Basic', 'Bearer', 'Negotiate', 'Certificate'];
$scope.authSchemeSupported = supportedSchemes.includes(authScheme);
if (authScheme === 'Bearer') {
diff --git a/solr/webapp/web/partials/login.html b/solr/webapp/web/partials/login.html
index 29c8c71..c21f262 100644
--- a/solr/webapp/web/partials/login.html
+++ b/solr/webapp/web/partials/login.html
@@ -76,6 +76,23 @@ limitations under the License.
WWW-Authenticate: {{wwwAuthHeader}}</pre>
<hr/>
</div>
+
+ <div ng-show="authScheme === 'Certificate'">
+ <h1>Certificate Authentication</h1>
+ <p>Your browser did not provide the required information to authenticate using PKI Certificates.
+ Please check that your computer has a valid PKI certificate for communicating with Solr,
+ and that your browser is properly configured to provide that certificate when required.
+ For more information, consult
+ <a href="https://lucene.apache.org/solr/guide/cert-authentication-plugin.html">
+ Solr's Certificate Authentication documentation
+ </a>.
+ </p>
+ The response from the server was:
+ <hr/>
+ <pre>HTTP 401 {{statusText}}
+WWW-Authenticate: {{wwwAuthHeader}}</pre>
+ <hr/>
+ </div>
<div ng-show="authScheme === 'Bearer'">
<h1>OpenID Connect (JWT) authentication</h1>