This is an automated email from the ASF dual-hosted git repository.
jdyer pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/branch_9x by this push:
new 88bde09e22e SOLR-15484 JWTAuthPluginIntegrationTest - dynamically
generate the self-signed certificate using the localhost loopback address
(#2099)
88bde09e22e is described below
commit 88bde09e22ef1f7e413f09fe03145220720a8a8e
Author: James Dyer <[email protected]>
AuthorDate: Mon Dec 11 08:02:59 2023 -0600
SOLR-15484 JWTAuthPluginIntegrationTest - dynamically generate the
self-signed certificate using the localhost loopback address (#2099)
SOLR-15484: dynamically generate the self-signed certificate using the
localhost loopback address
---
.../randomization/policies/solr-tests.policy | 4 +
solr/modules/jwt-auth/build.gradle | 2 +
.../apache/solr/security/jwt/JWTIssuerConfig.java | 17 +--
.../security/jwt/JWTAuthPluginIntegrationTest.java | 11 +-
.../solr/security/jwt/KeystoreGenerator.java | 116 +++++++++++++++++++++
5 files changed, 143 insertions(+), 7 deletions(-)
diff --git a/gradle/testing/randomization/policies/solr-tests.policy
b/gradle/testing/randomization/policies/solr-tests.policy
index 276532b519d..86871e72613 100644
--- a/gradle/testing/randomization/policies/solr-tests.policy
+++ b/gradle/testing/randomization/policies/solr-tests.policy
@@ -162,6 +162,10 @@ grant {
permission javax.security.auth.AuthPermission "createLoginContext.Server";
permission javax.security.auth.AuthPermission "createLoginContext.Client";
+ // Needed by BouncyCastle in jwt-auth tests
+ permission java.security.SecurityPermission "putProviderProperty.BC";
+ permission java.security.SecurityPermission
"getProperty.org.bouncycastle.x509.allow_non-der_tbscert";
+
// may only be necessary with Java 7?
permission javax.security.auth.PrivateCredentialPermission
"javax.security.auth.kerberos.KeyTab * \"*\"", "read";
permission javax.security.auth.PrivateCredentialPermission
"sun.security.jgss.krb5.Krb5Util$KeysFromKeyTab * \"*\"", "read";
diff --git a/solr/modules/jwt-auth/build.gradle
b/solr/modules/jwt-auth/build.gradle
index 1b420899e33..17886099e18 100644
--- a/solr/modules/jwt-auth/build.gradle
+++ b/solr/modules/jwt-auth/build.gradle
@@ -61,6 +61,8 @@ dependencies {
testImplementation 'com.fasterxml.jackson.core:jackson-databind'
permitTestUnusedDeclared 'com.fasterxml.jackson.core:jackson-databind'
+ testImplementation 'org.bouncycastle:bcpkix-jdk15on'
+ testImplementation 'org.bouncycastle:bcprov-jdk15on'
testImplementation 'com.nimbusds:nimbus-jose-jwt'
testImplementation 'com.squareup.okhttp3:mockwebserver'
testImplementation 'com.squareup.okhttp3:okhttp'
diff --git
a/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTIssuerConfig.java
b/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTIssuerConfig.java
index 947d040da8b..b7e650c1401 100644
---
a/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTIssuerConfig.java
+++
b/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTIssuerConfig.java
@@ -22,6 +22,7 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.invoke.MethodHandles;
+import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
@@ -423,6 +424,14 @@ public class JWTIssuerConfig {
return jwkConfigured > 0;
}
+ private static void disableHostVerificationIfLocalhost(URL url, Get httpGet)
{
+ InetAddress loopbackAddress = InetAddress.getLoopbackAddress();
+ if (loopbackAddress.getCanonicalHostName().equals(url.getHost())
+ || loopbackAddress.getHostName().equals(url.getHost())) {
+ httpGet.setHostnameVerifier((hostname, session) -> true);
+ }
+ }
+
public void setTrustedCerts(Collection<X509Certificate> trustedCerts) {
this.trustedCerts = trustedCerts;
}
@@ -472,9 +481,7 @@ public class JWTIssuerConfig {
if (trustedCerts != null) {
Get getWithCustomTrust = new Get();
getWithCustomTrust.setTrustedCertificates(trustedCerts);
- if ("localhost".equals(jwksUrl.getHost())) {
- getWithCustomTrust.setHostnameVerifier((hostname, session) -> true);
- }
+ disableHostVerificationIfLocalhost(jwksUrl, getWithCustomTrust);
httpsJkws.setSimpleHttpGet(getWithCustomTrust);
}
return httpsJkws;
@@ -525,9 +532,7 @@ public class JWTIssuerConfig {
Get httpGet = new Get();
if (trustedCerts != null) {
httpGet.setTrustedCertificates(trustedCerts);
- if ("localhost".equals(url.getHost())) {
- httpGet.setHostnameVerifier((hostname, session) -> true);
- }
+ disableHostVerificationIfLocalhost(url, httpGet);
}
SimpleResponse resp = httpGet.get(url.toString());
return parse(new
ByteArrayInputStream(resp.getBody().getBytes(StandardCharsets.UTF_8)));
diff --git
a/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginIntegrationTest.java
b/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginIntegrationTest.java
index 7a0700946c8..ef7fe35d4f5 100644
---
a/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginIntegrationTest.java
+++
b/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/JWTAuthPluginIntegrationTest.java
@@ -25,6 +25,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
+import java.net.InetAddress;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
@@ -102,6 +103,14 @@ public class JWTAuthPluginIntegrationTest extends
SolrCloudAuthTestCase {
pemFilePath =
JWT_TEST_PATH().resolve("security").resolve("jwt_plugin_idp_cert.pem");
wrongPemFilePath =
JWT_TEST_PATH().resolve("security").resolve("jwt_plugin_idp_wrongcert.pem");
+ Path tempDir =
Files.createTempDirectory(JWTAuthPluginIntegrationTest.class.getSimpleName());
+ tempDir.toFile().deleteOnExit();
+ Path modifiedP12Cert = tempDir.resolve(p12Cert.getFileName());
+ new KeystoreGenerator()
+ .generateKeystore(
+ p12Cert, modifiedP12Cert,
InetAddress.getLoopbackAddress().getCanonicalHostName());
+ p12Cert = modifiedP12Cert;
+
mockOAuth2Server = createMockOAuthServer(p12Cert, "secret");
mockOAuth2Server.start();
mockOAuthToken =
@@ -112,7 +121,7 @@ public class JWTAuthPluginIntegrationTest extends
SolrCloudAuthTestCase {
}
@AfterClass
- public static void afterClass() throws Exception {
+ public static void afterClass() {
if (mockOAuth2Server != null) {
mockOAuth2Server.shutdown();
}
diff --git
a/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/KeystoreGenerator.java
b/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/KeystoreGenerator.java
new file mode 100644
index 00000000000..6d2d6c9c23a
--- /dev/null
+++
b/solr/modules/jwt-auth/src/test/org/apache/solr/security/jwt/KeystoreGenerator.java
@@ -0,0 +1,116 @@
+/*
+ * 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.jwt;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.Security;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.Date;
+import org.bouncycastle.asn1.DERUTF8String;
+import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
+import org.bouncycastle.asn1.x500.RDN;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.cert.CertIOException;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+
+public class KeystoreGenerator {
+
+ private static final String PASS_PHRASE = "secret";
+
+ public void generateKeystore(Path existingKeystore, Path newKeystore, String
cn) {
+ KeyStore ks = null;
+ try (FileInputStream fis = new FileInputStream(existingKeystore.toFile()))
{
+ ks = KeyStore.getInstance(KeyStore.getDefaultType());
+ ks.load(fis, PASS_PHRASE.toCharArray());
+ ks.setCertificateEntry(cn, selfSignCertificate(cn));
+
+ } catch (KeyStoreException | CertificateException |
NoSuchAlgorithmException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ try (OutputStream fos = new FileOutputStream(newKeystore.toFile())) {
+ ks.store(fos, PASS_PHRASE.toCharArray());
+ } catch (CertificateException | KeyStoreException | IOException |
NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private X509Certificate selfSignCertificate(String commonName) {
+ try {
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
+ kpg.initialize(1024);
+ KeyPair kp = kpg.generateKeyPair();
+ Provider bcp = new BouncyCastleProvider();
+ Security.addProvider(bcp);
+
+ X500Name cn =
+ new X500Name(
+ new RDN[] {
+ new RDN(new AttributeTypeAndValue(BCStyle.CN, new
DERUTF8String(commonName)))
+ });
+ X500Name issuer =
+ new X500Name(
+ new RDN[] {
+ new RDN(new AttributeTypeAndValue(BCStyle.CN, new
DERUTF8String("Solr Root CA")))
+ });
+
+ Instant yesterday =
+
LocalDate.now(ZoneOffset.UTC).minusDays(1).atStartOfDay().toInstant(ZoneOffset.UTC);
+ Instant oneYear = ZonedDateTime.ofInstant(yesterday,
ZoneOffset.UTC).plusYears(1).toInstant();
+ BigInteger serial = new
BigInteger(String.valueOf(yesterday.toEpochMilli()));
+
+ JcaX509v3CertificateBuilder certBuilder =
+ new JcaX509v3CertificateBuilder(
+ issuer, serial, Date.from(yesterday), Date.from(oneYear), cn,
kp.getPublic());
+ BasicConstraints basicConstraints = new BasicConstraints(true);
+ certBuilder.addExtension(Extension.basicConstraints, true,
basicConstraints);
+
+ ContentSigner cs = new
JcaContentSignerBuilder("SHA256WithRSA").build(kp.getPrivate());
+ return new JcaX509CertificateConverter()
+ .setProvider(bcp)
+ .getCertificate(certBuilder.build(cs));
+ } catch (CertificateException
+ | CertIOException
+ | NoSuchAlgorithmException
+ | OperatorCreationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}