SOLR-15194: relax requirements and allow http urls. (#2430)

Relax the need for https urls for JWT IDP's if you pass in solr.auth.jwt.allowOutboundHttp=true system property.
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 5724064..e13ddc8 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -217,7 +217,7 @@
 * SOLR-14546: Fix for a relatively hard to hit issue in OverseerTaskProcessor that could lead to out of order execution
   of Collection API tasks competing for a lock (Ilan Ginzburg).
 
-* SOLR-15162: Allow readOnly parameter to be used with v2 modify collection command (Eric Pugh) 
+* SOLR-15162: Allow readOnly parameter to be used with v2 modify collection command (Eric Pugh)
 
 ==================  8.9.0 ==================
 
@@ -250,6 +250,8 @@
 * SOLR-15038: Add elevateOnlyDocsMatchingQuery and collectElevatedDocsWhenCollapsing parameters to query elevation.
   (Dennis Berger, Tobias Kässmann via Bruno Roustant)
 
+* SOLR-15194: Allow Solr to make outbound non SSL calls to a JWT IDP via -Dsolr.auth.jwt.allowOutboundHttp=true property. (Eric Pugh)  
+
 Optimizations
 ---------------------
 * SOLR-15079: Block Collapse - Faster collapse code when groups are co-located via Block Join style nested doc indexing.
diff --git a/solr/core/src/java/org/apache/solr/security/JWTIssuerConfig.java b/solr/core/src/java/org/apache/solr/security/JWTIssuerConfig.java
index e62915c..4e0e107 100644
--- a/solr/core/src/java/org/apache/solr/security/JWTIssuerConfig.java
+++ b/solr/core/src/java/org/apache/solr/security/JWTIssuerConfig.java
@@ -68,6 +68,9 @@
   private WellKnownDiscoveryConfig wellKnownDiscoveryConfig;
   private String clientId;
   private String authorizationEndpoint;
+  
+  public static boolean ALLOW_OUTBOUND_HTTP = Boolean.parseBoolean(System.getProperty("solr.auth.jwt.allowOutboundHttp", "false"));
+  public static final String ALLOW_OUTBOUND_HTTP_ERR_MSG = "HTTPS required for IDP communication. Please use SSL or start your nodes with -Dsolr.auth.jwt.allowOutboundHttp=true to allow HTTP for test purposes.";
 
   /**
    * Create config for further configuration with setters, builder style.
@@ -349,13 +352,16 @@
       this.jwkCacheDuration = jwkCacheDuration;
       this.refreshReprieveThreshold = refreshReprieveThreshold;
     }
-
+    
+    /**
+     * While the class name is HttpsJwks, it actually works with plain http formatted url as well.
+     * @param url the Url to connect to for JWK details.
+     */
     private HttpsJwks create(String url) {
       try {
         URL jwksUrl = new URL(url);
-        if (!"https".equalsIgnoreCase(jwksUrl.getProtocol())) {
-          throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, PARAM_JWKS_URL + " must use HTTPS");
-        }
+        checkAllowOutboundHttpConnections(PARAM_JWKS_URL, jwksUrl);
+        
       } catch (MalformedURLException e) {
         throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Url " + url + " configured in " + PARAM_JWKS_URL + " is not a valid URL");
       }
@@ -384,9 +390,11 @@
     public static WellKnownDiscoveryConfig parse(String urlString) {
       try {
         URL url = new URL(urlString);
-        if (!Arrays.asList("https", "file").contains(url.getProtocol())) {
-          throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Well-known config URL must be HTTPS or file");
+        if (!Arrays.asList("https", "file", "http").contains(url.getProtocol())) {
+          throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Well-known config URL must be one of HTTPS or HTTP or file");          
         }
+        checkAllowOutboundHttpConnections(PARAM_WELL_KNOWN_URL, url);
+
         return parse(url.openStream());
       } catch (MalformedURLException e) {
         throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Well-known config URL " + urlString + " is malformed", e);
@@ -435,4 +443,13 @@
       return (List<String>) securityConf.get("response_types_supported");
     }
   }
+
+  public static void checkAllowOutboundHttpConnections(String parameterName, URL url) {
+    if ("http".equalsIgnoreCase(url.getProtocol())) {
+      if (!ALLOW_OUTBOUND_HTTP) {
+        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, parameterName + " is using http protocol. " + ALLOW_OUTBOUND_HTTP_ERR_MSG);
+      }
+    }
+  }
+ 
 }
diff --git a/solr/core/src/test/org/apache/solr/security/JWTIssuerConfigTest.java b/solr/core/src/test/org/apache/solr/security/JWTIssuerConfigTest.java
index 3388552..7623f98 100644
--- a/solr/core/src/test/org/apache/solr/security/JWTIssuerConfigTest.java
+++ b/solr/core/src/test/org/apache/solr/security/JWTIssuerConfigTest.java
@@ -17,6 +17,9 @@
 
 package org.apache.solr.security;
 
+import static org.apache.solr.SolrTestCaseJ4.TEST_PATH;
+import static org.apache.solr.security.JWTAuthPluginTest.testJwk;
+
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
@@ -28,24 +31,22 @@
 import java.util.Map;
 
 import org.apache.commons.lang3.StringUtils;
+import org.apache.solr.SolrTestCase;
 import org.apache.solr.common.SolrException;
 import org.jose4j.jwk.JsonWebKeySet;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.noggit.JSONUtil;
 
-import static org.apache.solr.SolrTestCaseJ4.TEST_PATH;
-import static org.apache.solr.security.JWTAuthPluginTest.testJwk;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-public class JWTIssuerConfigTest {
+public class JWTIssuerConfigTest extends SolrTestCase {
   private JWTIssuerConfig testIssuer;
   private Map<String, Object> testIssuerConfigMap;
   private String testIssuerJson;
 
   @Before
   public void setUp() throws Exception {
+    super.setUp();
     testIssuer = new JWTIssuerConfig("name")
         .setJwksUrl("https://issuer/path")
         .setIss("issuer")
@@ -65,6 +66,12 @@
         "  \"iss\":\"issuer\",\n" +
         "  \"authorizationEndpoint\":\"https://issuer/authz\"}";
   }
+  
+  @After
+  public void tearDown() throws Exception {
+    super.tearDown();
+    JWTIssuerConfig.ALLOW_OUTBOUND_HTTP = false;
+  }  
 
   @Test
   public void parseConfigMap() {
@@ -126,6 +133,26 @@
   }
 
   @Test
+  public void jwksUrlwithHttpBehaviors() {
+    
+    HashMap<String, Object> issuerConfigMap = new HashMap<>();
+    issuerConfigMap.put("name", "myName");
+    issuerConfigMap.put("iss", "myIss");
+    issuerConfigMap.put("jwksUrl", "http://host/jwk");
+
+    JWTIssuerConfig issuerConfig = new JWTIssuerConfig(issuerConfigMap);
+    
+    SolrException e = expectThrows(SolrException.class, () -> issuerConfig.getHttpsJwks());
+    assertEquals(400, e.code());
+    assertEquals("jwksUrl is using http protocol. HTTPS required for IDP communication. Please use SSL or start your nodes with -Dsolr.auth.jwt.allowOutboundHttp=true to allow HTTP for test purposes.", e.getMessage());
+    
+    JWTIssuerConfig.ALLOW_OUTBOUND_HTTP = true;
+
+    assertEquals(1, issuerConfig.getHttpsJwks().size());
+    assertEquals("http://host/jwk", issuerConfig.getHttpsJwks().get(0).getLocation());    
+  }
+  
+  @Test
   public void wellKnownConfigFromInputstream() throws IOException {
     Path configJson = TEST_PATH().resolve("security").resolve("jwt_well-known-config.json");
     JWTIssuerConfig.WellKnownDiscoveryConfig config = JWTIssuerConfig.WellKnownDiscoveryConfig.parse(Files.newInputStream(configJson));
@@ -144,13 +171,27 @@
     assertEquals(Arrays.asList("code", "code id_token", "code token", "code id_token token", "token", "id_token", "id_token token"), config.getResponseTypesSupported());
   }
 
-  @Test(expected = SolrException.class)
-  public void wellKnownConfigNotHttps() {
-    JWTIssuerConfig.WellKnownDiscoveryConfig.parse("http://127.0.0.1:45678/.well-known/config");
-  }
+  @Test
+  public void wellKnownConfigWithHttpBehaviors() {
+    SolrException e = expectThrows(SolrException.class, () -> JWTIssuerConfig.WellKnownDiscoveryConfig.parse("http://127.0.0.1:45678/.well-known/config"));
+    assertEquals(400, e.code());
+    assertEquals("wellKnownUrl is using http protocol. HTTPS required for IDP communication. Please use SSL or start your nodes with -Dsolr.auth.jwt.allowOutboundHttp=true to allow HTTP for test purposes.", e.getMessage());
+    
+    JWTIssuerConfig.ALLOW_OUTBOUND_HTTP = true;
+    
+    e = expectThrows(SolrException.class, () -> JWTIssuerConfig.WellKnownDiscoveryConfig.parse("http://127.0.0.1:45678/.well-known/config"));
+    assertEquals(500, e.code());
+    // We open a connection in the code path to a server that doesn't exist, which causes this.  Should really be mocked.
+    assertEquals("Well-known config could not be read from url http://127.0.0.1:45678/.well-known/config", e.getMessage());
+    
+            
 
-  @Test(expected = SolrException.class)
+  }
+  
+  @Test
   public void wellKnownConfigNotReachable() {
-    JWTIssuerConfig.WellKnownDiscoveryConfig.parse("https://127.0.0.1:45678/.well-known/config");
+    SolrException e = expectThrows(SolrException.class, () -> JWTIssuerConfig.WellKnownDiscoveryConfig.parse("https://127.0.0.1:45678/.well-known/config"));
+    assertEquals(500, e.code());
+    assertEquals("Well-known config could not be read from url https://127.0.0.1:45678/.well-known/config", e.getMessage());
   }
 }
\ No newline at end of file
diff --git a/solr/solr-ref-guide/src/jwt-authentication-plugin.adoc b/solr/solr-ref-guide/src/jwt-authentication-plugin.adoc
index 5423e77..8eaadc3 100644
--- a/solr/solr-ref-guide/src/jwt-authentication-plugin.adoc
+++ b/solr/solr-ref-guide/src/jwt-authentication-plugin.adoc
@@ -161,6 +161,10 @@
 <12> Configure the audience claim. A token's 'aud' claim must match 'aud' for one of the configured issuers.
 <13> This issuer is auto configured through discovery, so 'iss' and JWK settings are not required
 
+=== Using non SSL URLs
+In production environments you should always use SSL protected HTTPS connections, otherwise you open yourself up to attacks.
+However, in development, it may be useful to use regular http urls, and bypass the
+security check that Solr performs. To support this you can set the environment variable `solr.auth.jwt.allowOutboundHttp=true`.
 
 == Editing JWT Authentication Plugin Configuration