You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ja...@apache.org on 2023/01/13 13:10:29 UTC

[solr] branch main updated: SOLR-16616: JWTAuthPlugin: Read trusted X509 certificates from multi files (#1284)

This is an automated email from the ASF dual-hosted git repository.

janhoy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new bb1c6371e85 SOLR-16616: JWTAuthPlugin: Read trusted X509 certificates from multi files (#1284)
bb1c6371e85 is described below

commit bb1c6371e855aab59d0c2fdffccf34c21a05e829
Author: Jan Høydahl <ja...@users.noreply.github.com>
AuthorDate: Fri Jan 13 14:10:22 2023 +0100

    SOLR-16616: JWTAuthPlugin: Read trusted X509 certificates from multi files (#1284)
---
 solr/CHANGES.txt                                   |  4 +-
 .../apache/solr/security/jwt/JWTAuthPlugin.java    | 72 +++++++++++++++-------
 .../solr/security/jwt/JWTAuthPluginTest.java       | 35 +++++++++++
 .../pages/jwt-authentication-plugin.adoc           |  4 +-
 4 files changed, 90 insertions(+), 25 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index a3268c3acd7..4c41b9abd74 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -118,7 +118,9 @@ Improvements
   
 * SOLR-10470: Introduce Builder setter for parallelCacheRefreshes on cloud SolrClients.  Deprecated
   direct setter setParallelCacheRefreshes on cloud SolrClients. (Eric Pugh, David Smiley, Alex Deparvu)
-  
+
+* SOLR-16616: JWTAuthPlugin: Read trusted X509 certificates from multiple files (janhoy)
+
 Optimizations
 ---------------------
 
diff --git a/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTAuthPlugin.java b/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTAuthPlugin.java
index 6c85d72ba46..9af08d6a655 100644
--- a/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTAuthPlugin.java
+++ b/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTAuthPlugin.java
@@ -17,7 +17,6 @@
 package org.apache.solr.security.jwt;
 
 import java.io.IOException;
-import java.io.InputStream;
 import java.lang.invoke.MethodHandles;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
@@ -78,7 +77,7 @@ import org.jose4j.lang.JoseException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-/** Authenticaion plugin that finds logged in user by validating the signature of a JWT token */
+/** Authentication plugin that finds logged in user by validating the signature of a JWT token */
 public class JWTAuthPlugin extends AuthenticationPlugin
     implements SpecProvider, ConfigEditablePlugin {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@@ -211,11 +210,10 @@ public class JWTAuthPlugin extends AuthenticationPlugin
       requiredScopes = Arrays.asList(requiredScopesStr.split("\\s+"));
     }
 
-    // Parse custom IDP SSL Cert from either path or string
-    InputStream trustedCertsStream = null;
-    String trustedCertsFile = (String) pluginConfig.get(PARAM_TRUSTED_CERTS_FILE);
+    // Parse custom IDP SSL Cert from either path, list of paths or plaintext string
+    Object trustedCertsFileObj = pluginConfig.get(PARAM_TRUSTED_CERTS_FILE);
     String trustedCerts = (String) pluginConfig.get(PARAM_TRUSTED_CERTS);
-    if (trustedCertsFile != null && trustedCerts != null) {
+    if (trustedCertsFileObj != null && trustedCerts != null) {
       throw new SolrException(
           SolrException.ErrorCode.SERVER_ERROR,
           "Found both "
@@ -224,25 +222,13 @@ public class JWTAuthPlugin extends AuthenticationPlugin
               + PARAM_TRUSTED_CERTS
               + ", please use only one");
     }
-    if (trustedCertsFile != null) {
-      try {
-        Path trustedCertsPath = Paths.get(trustedCertsFile);
-        if (coreContainer != null) {
-          coreContainer.assertPathAllowed(trustedCertsPath);
-        }
-        trustedCertsStream = Files.newInputStream(trustedCertsPath);
-        log.info("Reading trustedCerts from file {}", trustedCertsFile);
-      } catch (IOException e) {
-        throw new SolrException(
-            SolrException.ErrorCode.SERVER_ERROR, "Failed to read file " + trustedCertsFile, e);
-      }
+    if (trustedCertsFileObj != null) {
+      trustedSslCerts = readSslCertsFromFileOrList(trustedCertsFileObj);
     }
     if (trustedCerts != null) {
       log.info("Reading trustedCerts PEM from configuration string");
-      trustedCertsStream = IOUtils.toInputStream(trustedCerts, StandardCharsets.UTF_8);
-    }
-    if (trustedCertsStream != null) {
-      trustedSslCerts = CryptoKeys.parseX509Certs(trustedCertsStream);
+      trustedSslCerts =
+          CryptoKeys.parseX509Certs(IOUtils.toInputStream(trustedCerts, StandardCharsets.UTF_8));
     }
 
     long jwkCacheDuration =
@@ -297,6 +283,48 @@ public class JWTAuthPlugin extends AuthenticationPlugin
     lastInitTime = Instant.now();
   }
 
+  /**
+   * Given a configuration object of a file name or list of file names, read X509 certificates from
+   * each file
+   */
+  @SuppressWarnings("unchecked")
+  Collection<X509Certificate> readSslCertsFromFileOrList(Object trustedCertsFileObj) {
+    Collection<X509Certificate> certs = new HashSet<>();
+    List<String> trustedCertsFileList;
+    if (trustedCertsFileObj instanceof String) {
+      trustedCertsFileList = List.of((String) trustedCertsFileObj);
+    } else if (trustedCertsFileObj instanceof List) {
+      trustedCertsFileList = (List<String>) trustedCertsFileObj;
+    } else {
+      throw new SolrException(
+          SolrException.ErrorCode.SERVER_ERROR, "trustedCertsFile is neither a String or List");
+    }
+    log.info("Reading trustedCerts from file(s) {}", trustedCertsFileList);
+    trustedCertsFileList.forEach(
+        f -> {
+          try {
+            certs.addAll(parseCertsFromFile(f));
+          } catch (IOException e) {
+            throw new SolrException(
+                SolrException.ErrorCode.SERVER_ERROR, "Failed to read file " + f, e);
+          }
+        });
+    return certs;
+  }
+
+  /**
+   * Given a filename string, validate the file and then read any X509 certificates from it
+   *
+   * @return list of certificates found in file
+   */
+  Collection<? extends X509Certificate> parseCertsFromFile(String certFileName) throws IOException {
+    Path certFilePath = Paths.get(certFileName);
+    if (coreContainer != null) {
+      coreContainer.assertPathAllowed(certFilePath);
+    }
+    return CryptoKeys.parseX509Certs(Files.newInputStream(certFilePath));
+  }
+
   @SuppressWarnings("unchecked")
   private Optional<JWTIssuerConfig> parseIssuerFromTopLevelConfig(Map<String, Object> conf) {
     try {
diff --git a/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginTest.java b/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginTest.java
index 9fa5e09d252..9c5bd7144f8 100644
--- a/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginTest.java
+++ b/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginTest.java
@@ -665,4 +665,39 @@ public class JWTAuthPluginTest extends SolrTestCaseJ4 {
     assertEquals(
         2, CryptoKeys.parseX509Certs(IOUtils.toInputStream(cert, StandardCharsets.UTF_8)).size());
   }
+
+  @Test
+  public void readSslCertsFromFileOrList() {
+    String pemWithTwoCerts =
+        JWT_TEST_PATH()
+            .resolve("security")
+            .resolve("jwt_plugin_idp_cert.pem")
+            .toAbsolutePath()
+            .toString();
+    String pemWithOneCert =
+        JWT_TEST_PATH()
+            .resolve("security")
+            .resolve("jwt_plugin_idp_wrongcert.pem")
+            .toAbsolutePath()
+            .toString();
+    assertEquals(
+        3, plugin.readSslCertsFromFileOrList(List.of(pemWithTwoCerts, pemWithOneCert)).size());
+  }
+
+  @Test(expected = SolrException.class)
+  public void readSslCertsFromFileNotFound() {
+    String pemFilePath = JWT_TEST_PATH().resolve("not_exist.pem").toAbsolutePath().toString();
+    plugin.readSslCertsFromFileOrList(pemFilePath);
+  }
+
+  @Test
+  public void parseCertsFromFile() throws IOException {
+    String pemFilePath =
+        JWT_TEST_PATH()
+            .resolve("security")
+            .resolve("jwt_plugin_idp_cert.pem")
+            .toAbsolutePath()
+            .toString();
+    assertEquals(2, plugin.parseCertsFromFile(pemFilePath).size());
+  }
 }
diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/jwt-authentication-plugin.adoc b/solr/solr-ref-guide/modules/deployment-guide/pages/jwt-authentication-plugin.adoc
index 8bd7962ea1d..ec79558548e 100644
--- a/solr/solr-ref-guide/modules/deployment-guide/pages/jwt-authentication-plugin.adoc
+++ b/solr/solr-ref-guide/modules/deployment-guide/pages/jwt-authentication-plugin.adoc
@@ -64,7 +64,7 @@ claimsMatch          ; JSON object of claims (key) that must match a regular exp
 adminUiScope         ; Define what scope is requested when logging in from Admin UI ; If not defined, the first scope from `scope` parameter is used
 redirectUris         ; Valid location(s) for redirect after external authentication. Takes a string or array of strings. Must be the base URL of Solr, e.g., https://solr1.example.com:8983/solr/ and must match the list of redirect URIs registered with the Identity Provider beforehand. ; Defaults to empty list, i.e., any node is assumed to be a valid redirect target.
 trustedCerts         ; One or more X.509 SSL certificates in plaintext PEM or PKCS#7 formats, that should be trusted when talking to IdPs. Newlines must be replaced with `\n`. See paragraph <<Trusting the IdP server>> for more about its usage. ; Defaults to Java truststore
-trustedCertsFile     ; Path to a file of type PEM, DER or PKCS#7, containing one or more X.509 SSL certificates that should be trusted when talking to IdPs. See paragraph <<Trusting the IdP server>> for more about its usage. ; Defaults to Java truststore
+trustedCertsFile     ; Path to a file of type PEM, DER or PKCS#7, containing one or more X.509 SSL certificates that should be trusted when talking to IdPs. Can also be an array of file paths. See paragraph <<Trusting the IdP server>> for more about its usage. ; Defaults to Java truststore
 issuers              ; List of issuers (Identity providers) to  support. See section <<issuer-configuration,Issuer configuration>> for configuration syntax ;
 |===
 
@@ -193,7 +193,7 @@ This is both more secure and also lets you trust self-signed certificates.
 It also has the benefit of working even if Solr is not started in SSL mode.
 
 Please configure either the `trustedCerts` or `trustedCertsFile` option.
-Configuring both will cause an error.
+Configuring both will cause an error. If `trustedCertsFile` is an array of strings, Solr will parse certificates from all files.
 
 === Multiple Authentication Schemes